归并排序
自我感觉就是二分答案的表弟
总的来说,归并排序是可以这样看的:
将一段数字组成的序列分成一小段一小段的,然后将每小段都依次合并起来,合并的过程中,是依次比较两小段的第一个然后将小的那一个放入一个数组中先存起来,这个时候可以用一个记录下标或者指针的东西将小的那一个所在的小段的头指针向后移动一个代表这个跳了出来(类似队列的思想),这样就可以将两个小的数字组成的段排序成为一个大的数组的段,如果小的是有序的,那么大的排出来也一定是有序的,所以最小的就可以从1开始这样一直排到整个数字组成的段的长度的时候,就会完美的出现一个有序的段了。
复杂度 O(nlogn)
(扩展一下:sort复杂度O(nlogn))
这个复杂度和sort的复杂度是差不多的,而且sort多方便啊,为什么还要学归并排序呢?
答案就在这里:
求逆序对!
首先科普一下什么是逆序对
逆序对真是个好东西
逆序对就是序列中ai>aj且i<j的有序对。
NOIP2013火柴排队就用到了这个,所以还是很有学的必要
然后开始说怎么用归并排序求逆序对
逆序对是可以在归并排序的过程中顺便就可以求出来的
当我们正在比较两个小段的时候,就比如a[]和b[]这两个数组储存的数字们
假设a在b前面的(因为递归的时候需要分成两段,合并的时候也还是分开的那两段合并所以这两个是肯定有个先后的)
如果a[tota] <= b[totb],那么就先吧a[tota]放入另一个数组c的中(按顺序放哦),然后tota++
这个时候,不符合逆序对
那么当a[tota] > b[totb],这个时候将b[totb]按照上面的处理一下,这个时候因为a在b的前所以tota是一定小于totb的,而且a[tota] > b[totb]符合逆序对,这个时候重点来了
因为a[tota] > b[totb],并且a[tota]在a数组中,后面的都是比a[tota]大的,所以a后面的也会比b[totb]大,就理所当然的可以组成逆序对了,这里就可以计数一下,加上后面符合的数量
最后输出计的数就可以了
完整代码
#include<iostream>
#include<cstdio>
using namespace std;
const int Max = 500005;
int a[Max];
int c[Max];
long long ans = 0;
void sort(int x,int y)//归并排序
{
if(x == y)return;//递归到只有一个数的时候回溯回去
int mid = (x + y) >> 1;//去中间数
sort(x,mid);sort(mid + 1,y);//类似二分答案的递归
int i = x,j = mid + 1;
int k = x;
while(i <= mid && j <= y)//排序
{
if(a[i] <= a[j])c[k ++] = a[i ++];
else c[k ++] = a[j ++],ans += mid - i + 1;
}
while(i <= mid)//把剩余的放进去
c[k ++] = a[i ++];
while(j <= y)
c[k ++] = a[j ++];
for(int i = x;i <= y;++ i)//修改一下
a[i] = c[i];
}
int main()
{
int n;
scanf("%d",&n);
for(int i = 1;i <= n;++ i)scanf("%d",&a[i]);
sort(1,n);
cout<<ans<<endl;
return 0;
}