前言
只是记录
思路
- 什么是逆序对?
在一个序列里,对于第 i 个和第 j 个元素,如果满足 i<j 且 a[i] > a[j],那么 (a[i],a[j[)就是一个逆序对。要注意的是: 如果有 k<i<j 且 a[k] > a[i] > a[j],那么 (a[k],a[i]) 和(a[k],a[j])是两个逆序对。
举个栗子:
序列 | 逆序对 |
---|---|
1 3 2 | (3,2) |
2 3 1 | (2,1) ,(3,1) |
- 归并排序
- 将[l,r] => [l,mid] + [mid+1,r]
- 将两个区间递归排序
- 合并
- 将逆序对分为三类
- 两个元素都在左区间
- 两个元素都在右区间
- 两个元素一个在左,一个在右
- 所以在计算的时候需要
- 递归算左边的
- 递归算右边的
- 算一左一右的
- 加起来
- 注意: 左右两边的元素在各自任意调换顺序,是不影响第三步计数的,因此我们可以数完就给它排序。这么做的好处在于,如果序列是有序的,会让第三步计数很容易。
举个栗子:
4 5 6 | 1 2 3
4 >1,左边最小的数都大于1,那4后面的数肯定都大于1,所以直接算mid-i+1(也就是左区间有几个数就行。为什么要+1:就像1 2 3 有 (3-1)+1 个数)。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
long long merge_sort(int q[], int l, int r)//用int不会过
{
if(l>=r)// if(l==r) 也可以
return 0;
int temp[N];
int mid=l+r>>1;// => (l+r)/2
int k=0,i=l,j=mid+1;//分成两个区间
long long ans=0;
ans+=merge_sort(q,l,mid)+merge_sort(q,mid+1,r);//递归排序并求逆序对数量
while(i<=mid&&j<=r)
{
if(q[i]<=q[j])
temp[k++]=q[i++];
else // i<j 且 q[i]>q[j]
{
temp[k++]=q[j++];
ans+=mid-i+1;//因为 i 指向的数比 j 指向的数大,所以[i,mid]指向的的数比 j 指向的数都大,直接算有几个数(mid-i+1)
}
}
//左右区间中至少有一个区间的数已经全部放到temp数组了
while(i<=mid)//左边区间的数还有剩余的,直接放到temp里
temp[k++]=q[i++];
while(j<=r)//右边区间还有剩余的,直接放到temp里
temp[k++]=q[j++];
for(i=l,j=0;i<=r;i++,j++)
q[i]=temp[j];
return ans;
}
int main()
{
int n;
cin>>n;
int q[N];
for(int i=0;i<n;i++)
cin>>q[i];
long long ans=merge_sort(q,0,n-1);
cout<<ans<<endl;
return 0;
}