归并排序是分治法的一个最经典也是最基础的应用
Divide And Conquer的思想很重要
归并排序的的Divide采用了简单的二分 Conquer采用的是将两个有序数组合并为一个有序数组。
2014-Inverse number:Reborn 逆序数求解
#include <algorithm> #include <iostream> #include <cstring> using namespace std; const int maxlen=1e6 + 10; long long ans; int ar[maxlen]; void Merge(int begin,int end){ int temp[maxlen]; int i,j,k,p,q; int middle=(begin+end)/2; p=begin; q=middle+1; k=begin; while(p<=middle && q<=end){ if(ar[p]<=ar[q]) temp[k++]=ar[p++]; else { temp[k++]=ar[q++]; ans+=middle-p+1; } } while(p<=middle) temp[k++]=ar[p++]; while(q<=end) temp[k++]=ar[q++]; for(k=begin;k<=end;k++) ar[k]=temp[k]; } void MergeSort(int begin,int end){ int i,j; if(begin < end){ int middle=(begin + end)/2; MergeSort(begin,middle); MergeSort(middle+1,end); Merge(begin,end); } } int main(){ int n,i,j,k; while(~scanf("%d",&n)){ for(i=0;i<n;i++) scanf("%d",&ar[i]); ans=0; MergeSort(0,n-1); printf("%lld\n",ans); } return 0; }
利用归并排序来求解逆序数最巧妙的地方是在原本的合并两个有序数组的过程中,借用了有序的思想。
如果将一个数组分为两部分,那么逆序数只可能在左右两部分或者横跨两部分中出现,显然在子数组都有序的情况下。
逆序数只会出现在横跨两部分之间,那么其实在合并两个子数组的过程中,就可以顺便统计
核心代码:
if(ar[p]<=ar[q]) temp[k++]=ar[p++]; else { temp[k++]=ar[q++]; ans+=middle-p+1; }
2015-模式寻对 几乎是归并排序的翻版
但值得注意的是在归并排序的过程中,其实也在修改数组的内容,记得要复原,或者使用其他方法来保存。
2015-中等·Magry恼人的词典编辑
这道题用到了一个线性代数里的定理,即交换最小次数使一个序列非降序的次数 等于其序列本身的逆序数。所以这道题其实本质上还是求解一个逆序数,要注意既然是字符串那么可以使用string (cin>>)
#include <algorithm> #include <iostream> #include <cstring> using namespace std; const int maxlen=1e5 + 10; long long ans; string ar[maxlen]; string temp[maxlen]; void Merge(int begin,int end){ int i,j,k,p,q; int middle=(begin+end)/2; p=begin; q=middle+1; k=begin; while(p<=middle && q<=end){ if(ar[p]<=ar[q]) temp[k++]=ar[p++]; else { temp[k++]=ar[q++]; ans+=middle-p+1; } } while(p<=middle) temp[k++]=ar[p++]; while(q<=end) temp[k++]=ar[q++]; for(k=begin;k<=end;k++) ar[k]=temp[k]; } void MergeSort(int begin,int end){ int i,j; if(begin < end){ int middle=(begin + end)/2; MergeSort(begin,middle); MergeSort(middle+1,end); Merge(begin,end); } } int main(){ int n,i,j,k; int T; cin >> T; while(T--){ cin >> n; for(i=0;i<n;i++) cin >> ar[i]; ans=0; MergeSort(0,n-1); printf("%lld\n",ans); } return 0; }
2016-D&C--玲珑数 (逆序数的进阶版)
这道题在归并排序的基础上做了很大的改变
关键点是限制了条件后,无法使用排序过程的trick,只能在归并排序的基础上,利用有序性移动指针来减少部分时间损耗。
#include <algorithm> #include <iostream> #include <cstring> using namespace std; const int maxlen=1e6 + 10; long long ans; long long ar[maxlen]; long long br[maxlen]; void Merge(long long begin,long long end){ long long temp[maxlen]; long long i,j,k,p,q; long long middle=(begin+end)/2; p=begin; q=middle+1; k=begin; while(p<=middle && q<=end){ if(ar[p]<=ar[q]) temp[k++]=ar[p++]; else temp[k++]=ar[q++]; } while(p<=middle) temp[k++]=ar[p++]; while(q<=end) temp[k++]=ar[q++]; for(k=begin;k<=end;k++) ar[k]=temp[k]; } void MergeSort(long long begin,long long end){ int i,j; if(begin < end){ int middle=(begin + end)/2; MergeSort(begin,middle); MergeSort(middle+1,end); j=middle+1; for(i=begin;i<=middle;i++){ while(j<=end && ar[i]>2*ar[j]) j++; ans+=j-(middle+1); } Merge(begin,end); } } int main(){ long long n,i,j,k,t,x,y; while(~scanf("%lld",&n)){ for(i=0;i<n;i++) scanf("%lld",&br[i]); scanf("%lld",&t); while(t--){ ans=0; scanf("%lld %lld",&x,&y); if(x>y) swap(x,y); for(i=x;i<=y;i++) ar[i]=br[i]; MergeSort(x,y); printf("%lld\n",ans); } } return 0; }
关键代码是利用有序性的双指针移动遍历法。0(n)
j=middle+1; for(i=begin;i<=middle;i++){ while(j<=end && ar[i]>2*ar[j]) j++; ans+=j-(middle+1); }
还有两个小的坑点吧,一是给定的区间是p和q,但是前后次序却没有给定,必要时要通过swap函数来调整。
还有就是一个老生常谈的问题2*int 可能会爆int 解决办法就是能long long 绝不int
2017-序列优美差值 (再度进阶版)
给定一个序列a,询问满足i<ji<j且 L≤a[j]−a[i]≤RL≤a[j]−a[i]≤R的点对(i,j)(i,j)数量
#include <algorithm> #include <iostream> #include <cstring> using namespace std; const int maxlen=1e6 + 10; long long ans; long long ar[maxlen]; long long br[maxlen]; long long L,R; void Merge(long long begin,long long end){ long long temp[maxlen]; long long i,j,k,p,q; long long middle=(begin+end)/2; p=begin; q=middle+1; k=begin; while(p<=middle && q<=end){ if(ar[p]<=ar[q]) temp[k++]=ar[p++]; else temp[k++]=ar[q++]; } while(p<=middle) temp[k++]=ar[p++]; while(q<=end) temp[k++]=ar[q++]; for(k=begin;k<=end;k++) ar[k]=temp[k]; } void MergeSort(long long begin,long long end){ int i,j; if(begin < end){ int middle=(begin + end)/2; MergeSort(begin,middle); MergeSort(middle+1,end); j=middle+1; int st,ed; st=ed=begin; for(i=middle+1;i<=end;i++){ while(ar[i]-ar[ed] >= L && ed<=middle) ed++; while(ar[i]-ar[st] > R && st<=middle) st++; ans+=ed-st; } Merge(begin,end); } } int main(){ long long n,i,j,k,t,x,y,T; scanf("%lld\n",&T); while(T--){ scanf("%lld %lld %lld",&n,&L,&R); for(i=0;i<n;i++) scanf("%lld",&ar[i]); ans=0; MergeSort(0,n-1); printf("%lld\n",ans); } return 0; }
这个问题的求解十分的巧妙 利用归并排序来求解问题 就要想办法利用有序性,本题是双指针移动,第一步求解其实已经利用一个数组的单调性,st和ed的单向移动利用了另一个数组的单调性,非常巧妙。
2017—数组优美和值(前缀和 + 分治排序)