逆序对的定义:对于数列的第 i 个和第 j 个元素,如果满足 i < j 且 a[i] > a[j],则其为一个逆序对。
重要的地方在于,一个元素可以不只是在一个逆序对中存在。如果 k > j > i 且 a[i] > a[j] > a[k],那么这里有两个含 a[i] 的逆序对,分别是 (a[i], a[j]) 和 (a[i], a[k]), a[i]是可以使用多次的。
分析问题,这里我们可以使用分治法解决问题。
将序列从中间分开,将逆序对分成三类:
两个元素都在左边;
两个元素都在右边;
两个元素一个在左一个在右;
计算逆序对的数量(序列):
1. 递归算左边的;
2. 递归算右边的;
3. 算一个左一个右的;
4. 把他们加到到一起。
这个时候我们注意到一个很重要的性质,左右半边的元素在各自任意调换顺序,是不影响第三步计数的,因此我们可以数完就给它排序。这么做的好处在于,如果序列是有序的,会让第三步计数很容易,这也是为什么最后必须像标准归并一样把新数组tmp的数填回q数组。
如果无序暴力数的话这一步是O(n^2)的。
关于为什么可以用归并解决且本质都是一左一右,y总思想:
先假定归并排序把序列进行排序并返回逆序对的数量。
然后当两个数在左边或者右边的时候就是直接调用归并排序返回。
当出现一个在左,一个在右的时候,就是第三种情况,利用多路归并算法进行求解。
而前面两种由于递归的性质最后都会变成第三种情况。
还要额外注意一个问题就是数据溢出,本题要用long long 类型,因为考虑逆序数对的数量最大情况(数据是从大到小排列的),那么就是n-1+n-2+n-3+...+1,等差数列求和,(n-1)n=O(n^2),n=100000,n^2=10^10.
#include<iostream>
using namespace std;
const int N=100010;
typedef long long LL;
int q[N],tmp[N];
LL mergesort(int q[],int l,int r){
if(l>=r) return 0;
int mid=l+r>>1;
LL res=mergesort(q,l,mid)+mergesort(q,mid+1,r);
int i=l,j=mid+1,x=q[l+r>>1],k=0;
while(i<=mid&&j<=r){
if(q[i]<=q[j]){
tmp[k++]=q[i++];
}
else{
tmp[k++]=q[j++];
res+=mid-i+1;
}
}
while(i<=mid) tmp[k++]=q[i++];
while(j<=r) tmp[k++]=q[j++];
for(i=l,j=0;i<=r;i++,j++) q[i]=tmp[j];
return res;
}
int main(){
int n;
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d",&q[i]);
cout<<mergesort(q,0,n-1)<<endl;
return 0;
}