788-逆序对的数量

题目描述

关系

内容

给定一个长度为 n 的整数数列,请你计算数列中的逆序对的数量。

逆序对的定义如下:对于数列的第 i 个和第 j 个元素,如果满足 i<ja[i]>a[j],则其为一个逆序对;否则不是。

输入格式

第一行包含整数 n,表示数列的长度。

第二行包含 n 个整数,表示整个数列。

输出格式

输出一个整数,表示逆序对的个数。

数据范围

1n100000
数列中的元素的取值范围 [1,109]

输入样例:

6
2 3 4 5 6 1

输出样例:

5

问题分析

最初思路

思路分析

先假定归并排序把序列进行排序并返回逆序对的数量。
然后当两个数在左边或者右边的时候就是直接调用归并排序返回。
当出现一个在左,一个在右的时候,就是第三种情况,利用多路归并算法进行求解。
而前面两种由于递归的性质最后都会变成第三种情况。


为什么排序之后不会改变逆序对的个数?

利用黑盒的思路来理解。首先,我们假设归并分治的过程,能够处理过左右两边的逆序对的个数。现在的关键就是合并左右两边的结果。

逆序对有三种情况:

  1. 逆序对在左半边或者右半边,这已经由子过程处理完毕了;
  2. 逆序对位于左右两半边;

对于第二种情况,我们我们要想办法把合并起来,得出这种情况的个数。

我们可以发现一个重要的性质,就是无论左半边(右半边)的元素怎么调换位置,都不会影响第二种情况的逆序对个数。所以归并分治的过程就得以成立。我们就可以对左半边和右半边的元素进行排序。

执行流程设计

总结

代码实现

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N = 1e5 + 10;
int n;
int a[N], t[N];

ll merge_sort(int a[], int l, int r) {
    if (l >= r) return 0;
    
    int mid = l + r >> 1;
    ll res = merge_sort(a, l, mid) + merge_sort(a, mid + 1, r);
    
    int k = 0, i = l, j = mid + 1;
    while (i <= mid && j <= r) {
        if (a[i] <= a[j]) t[k++] = a[i++];
        else {
            t[k++] = a[j++];
            res += mid - i + 1;
        }
    }
    
    while (i <= mid) t[k++] = a[i++];
    while (j <= r) t[k++] = a[j++];
    
    for (int i = l, j = 0; i <= r; i++, j++) a[i] = t[j];
    return res;
}

int main()
{
    cin >> n;
    
    for (int i = 0; i < n; i++) cin >> a[i];
    
    cout << merge_sort(a, 0, n - 1) << endl;
    
    return 0;
}