歸並排序
自我感覺就是二分答案的表弟
總的來說,歸並排序是可以這樣看的:
將一段數字組成的序列分成一小段一小段的,然后將每小段都依次合並起來,合並的過程中,是依次比較兩小段的第一個然后將小的那一個放入一個數組中先存起來,這個時候可以用一個記錄下標或者指針的東西將小的那一個所在的小段的頭指針向后移動一個代表這個跳了出來(類似隊列的思想),這樣就可以將兩個小的數字組成的段排序成為一個大的數組的段,如果小的是有序的,那么大的排出來也一定是有序的,所以最小的就可以從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;
}