2017級算法模擬上機準備篇(歸並排序)
阿新 • • 發佈:2018-12-13
-c lse long swap col n) divide div ram
歸並排序是分治法的一個最經典也是最基礎的應用
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(intbegin,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-模式尋對 幾乎是歸並排序的翻版
但值得註意的是在歸並排序的過程中,其實也在修改數組的內容,記得要復原,或者使用其他方法來保存。
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—數組優美和值(前綴和 + 分治排序)
2017級算法模擬上機準備篇(歸並排序)