归并排序
本着别人听懂了自己才算会的原则,今天就在这里丢人现眼一回(灬ꈍ ꈍ灬)。
题意要求找出所有的逆序对。
首先就要明确两点:
- 仅仅对其进行排序然后记录执行次数是于事无补的。
- 暴力数的话时间复杂度是一定通不过的。
解决方案:
- 将注意力聚焦到归并排序上(也可参考 排序系列 )。
- 归并排序的特点==>先将整个数列拆成小块,然后排序合并。
- 分析一种情况,如果“左块”的某个值大于“右块”的某个值,那么可以确定“左块”该值右边的所有值都大于“右块”的这个值(因为“左右两块”都是有序的,这里假定为正序(由小到大))。那么“左块”的这个值及其右边的所有值都与“右块”的这个值构成逆序对。写在程序中就是ans+=mid-x+1;
- 每一次归并排序第一个循环结构至少会收容下完整的“一块”数列。如果“左块”被完全收容,说明“右块”右边有几个值是两块中最大的,不满足逆序对。如果“右块”被完全收容,说明“左块”右边有几个值是两块中最大的,但是由于“右块”被完全收容,所以满足条件的逆序对都已经添加了。
- 所以每一次归并排序其实是得到满足“右块”所有值的逆序对。上面分析了不论是“左收容”还是“右收容”都能达到这个要求。
- 形象来说,小块排序合并时是处理小块逆序对,大块排序合并时是处理大块逆序对。所以对于每个值考虑了它的所有逆序对(由近及远),用一种巧妙的方式(哦,顺便还排个序!)。
好吧!我摊牌了,我讲不清楚!o(╥﹏╥)o
注意点:
- 排序本身时间复杂度不会崩,数组长度不会崩,但ans用int是一定会崩的。
具体代码如下:
1 #include<iostream> 2 #include<cstring> 3 using namespace std; 4 5 const int N=1e5+5; 6 int a[N]; 7 long long ans; 8 //归并排序 9 void mergeSort(int begin,int end){ 10 //递归终止条件 11 if(begin==end)return; 12 13 int mid=(begin+end)/2,t[N]; 14 mergeSort(begin,mid); 15 mergeSort(mid+1,end); 16 int x=begin,y=mid+1,r=begin; 17 while(x<=mid&&y<=end){ 18 if(a[x]>a[y]){ 19 //核心算法 20 ans+=mid-x+1; 21 t[r++]=a[y++]; 22 }else{ 23 t[r++]=a[x++]; 24 } 25 } 26 while(x<=mid){ 27 t[r++]=a[x++]; 28 } 29 while(y<=end){ 30 t[r++]=a[y++]; 31 } 32 //复制数组 33 while(--r>=begin){ 34 a[r]=t[r]; 35 } 36 } 37 int main(){ 38 cin>>a[0]; 39 for(int i=1;i<=a[0];i++)cin>>a[i]; 40 mergeSort(1,a[0]); 41 cout<<ans; 42 return 0; 43 }