题目链接

AcWing 788.逆序对的数量

引用

看了大佬的题解后对于归并又有了更深的理解,在这里引用一下dongwa_zzuli大佬的题解AcWing 788. 逆序对的数量,感兴趣的小伙伴可以看看

题目描述

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

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

样例

输入格式

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

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

输出格式

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

数据范围

1n1000001≤n≤100000

输入样例:

6
2 3 4 5 6 1

输出样例:

5

算法

(归并排序) O(nlogn)O(nlogn)

做这道题的话因为序列是不能被打乱的,而归并排序开始排序之前,序列还是原来的样子。根据这道题的性质我们可以发现,一共有三种情况,也就是这两个数的位置。

  1. 刚好在左区间
  2. 刚好在右区间
  3. 大的数在左区间,小的数在右区间

前两个情况很好求,在我们处理左右区间时就可以求出来,那么第三种呢。其实也不难,根据归并的性质可以知道在归并到最后一个区间的时候左右两个子区间是有序的,那么我们归并的时候可以判断一下,如果左指针所指的数大于右指针所指的数,那么左指针后的所有数都将大于它,那么这个数构成的逆序对就是 mid - i + 1 ,那么只需将所有这种情况加起来,再加上前两种情况就行了。前两种情况其实在合并为将它们分开时的那个区间时就已经计算完了,只需把 merge_sort(q , l , mid)merge_sort(q , mid + 1 , r) 这两个结果加起来就行了,虽然看上去有点绕,但是如果你知道归并的过程的话,其实就能明白了,还是不太清楚的话建议手动模拟一下,也可以看看我画的图

图解逆序对

其实这里还有一个很小的细节,因为我们需要存储逆序对的数量,当序列是倒序的时候逆序列是最多的,即对 n1+n2+n3...+1n-1 + n-2 + n -3 ... + 1 ==>> i=1nn\sum_{i=1}^nn 也就是 n(n1)2\frac{n(n - 1)}{2} ,当n为 1e51e5 时,逆序对的数量为 5×1095×1045\times10^9-5\times10^4,已经超出int的范围了所以我们应该用long或者long long存

时间复杂度 O(nlogn)O(nlogn)

一共划分 lognlogn 次区间,每一层都是n次,所以时间复杂度是 O(nlogn)O(nlogn)

C++ 代码

#include <cstdio>

using namespace std;

typedef long long LL;

const int N = 1e5 + 10;

int n;
int a[N];

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

    return res;
} 

int main()
{
    scanf("%d" , &n);
    
    for(int i = 0; i < n; ++ i) scanf("%d" , &a[i]);
    
    LL res = merge_sort(a , 0 , n - 1);
    
    printf("%lld" , res);
    
    return 0;
}