逆序對的三種求法(歸併排序,樹狀陣列,線段樹)
阿新 • • 發佈:2018-11-02
求逆序對個數的三種方法
逆序對: 對於一個序列 $a_1$,$a_2$,$a_3$..$a_n$,如果存在$a_i$>$a_j$且i<j,則$a_i$和$a_j$為一個逆序對。
這裡將介紹3種求逆序對對數的方法。
在此之前,預設為你已經會了歸併排序,樹狀陣列和線段樹。(不會的可以百度學習一下)
學會後可以試著做一下這道題求逆序對
1.歸併排序求逆序對
這應該是幾乎人人都會的方法,原理是利用歸併排序時,對於區間l~r
,在mid
左邊和mid
右邊都已經是單調的序列來求逆序對,這種方法就不多講了。時間複雜度約為O(nlogn)
程式碼如下:個人習慣打快讀
#include<iostream> using namespace std; const int maxn=1e6+10; template<typename T> void read(T&res) { char ch=getchar(); T q=1; while(ch<'0' or ch>'9') { if(ch=='-')q=-1; ch=getchar(); } res=(ch^48); while((ch=getchar())>='0' and ch<='9') res=(res<<1)+(res<<3)+(ch^48); res*=q; return; } int n,a[maxn]; long long ans; inline void init() { read(n); for(int i=1;i<=n;i++) read(a[i]); } //歸併排序 int temp[maxn]; void merge_sort(int l,int r) { if(l==r)return; int mid=l+r>>1; merge_sort(l,mid); merge_sort(mid+1,r); int ll=l,st=l,rr=mid+1; while(l<=mid and rr<=r) { if(a[l]<=a[rr]) temp[ll++]=a[l++]; else temp[ll++]=a[rr++],ans+=mid-l+1; } while(l<=mid)temp[ll++]=a[l++]; while(rr<=r)temp[ll++]=a[rr++]; for(int i=st;i<=r;i++) a[i]=temp[i]; return; } int main() { init(); merge_sort(1,n); cout<<ans<<endl; return 0; }
2.樹狀陣列求逆序對
樹狀陣列求逆序對也是一種很好的方法,它在求整個序列的逆序對的同時也可以求出每一個$a_i$的逆序對個數,
並且程式碼也比較短。 時間複雜度約為 O(nlogn)
具體方法:
我們知道樹狀陣列可以快速的進行區間修改和區間查詢,那麼我們就要充分利用它的優點。
我的做法是:
用陣列tree表示$a_i$的逆序對對數,倒過來掃一遍,邊掃邊把答案求出,並把比$a_i$大的每一個數字的逆序對對數都新增1。
樹狀陣列我就不壓行了
具體做法如下:
#include<iostream> using namespace std; const int maxn=1e6+10; template<typename T> void read(T&res) { char ch=getchar(); T q=1; while(ch<'0' or ch>'9') { if(ch=='-')q=-1; ch=getchar(); } res=(ch^48); while((ch=getchar())>='0' and ch<='9') res=(res<<1)+(res<<3)+(ch^48); res*=q; return; } int n,a[maxn]; long long ans; inline void init() { read(n); for(int i=1;i<=n;i++) read(a[i]); } //樹狀陣列 int tree[maxn]; inline int lowbit(int x){return x&-x;} inline void add(int x,int v) { while(x<=n) { tree[x]+=v; x+=lowbit(x); } return; } inline int sum(int x) { int res=0; while(x>=1) { res+=tree[x]; x-=lowbit(x); } return res; } void solve() { for(int i=n;i>=1;i--) ans+=sum(a[i]), add(a[i],1);//可以同時記錄下每個數字的逆序對 cout<<ans<<endl; } int main() { init(); solve(); return 0; }
3.權值線段樹
我們可以把線段樹中的l,r
表示為l~r
中的每一個數字出現了的總數(l=r時,就是單個數字的個數),在建樹時,不新增任何值,做一顆空樹,掃一遍的時候新增數字進去。也可以計算出每個數字的逆序對個數
時間複雜度為 O(2nlogn)
具體做法如下:
#include<iostream> using namespace std; const int maxn=1e6+10; template<typename T> void read(T&res) { char ch=getchar(); T q=1; while(ch<'0' or ch>'9') { if(ch=='-')q=-1; ch=getchar(); } res=(ch^48); while((ch=getchar())>='0' and ch<='9') res=(res<<1)+(res<<3)+(ch^48); res*=q; return; } int n,a[maxn]; long long ans; inline void init() { read(n); for(int i=1;i<=n;i++) read(a[i]); } //權值線段樹 struct{ int l,r,tot; }e[maxn*3]; void build(int k,int l,int r) { e[k].l=l,e[k].r=r; if(l==r)return; int mid=(l+r)>>1; build(k<<1,l,mid); build(k<<1|1,mid+1,r); } void update(int k,int num) { if(e[k].l==num and e[k].r==num) { e[k].tot++; return; } int mid=(e[k].l+e[k].r)>>1; if(num<=mid)update(k<<1,num); if(num>mid)update(k<<1|1,num); e[k].tot=e[k<<1].tot+e[k<<1|1].tot; return; } long long sum(int k,int l,int r) { if(e[k].l>r or e[k].r<l)return 0; if(e[k].l==l and e[k].r==r)return e[k].tot; int mid=e[k].l+e[k].r>>1; if(mid>=r)return sum(k<<1,l,r); if(mid<l)return sum(k<<1|1,l,r); return sum(k<<1,l,mid)+sum(k<<1|1,mid+1,r); } void solve() { build(1,1,maxn); for(int i=1;i<=n;i++) { ans+=sum(1,a[i]+1,maxn); update(1,a[i]); } cout<<ans<<endl; } int main() { init(); solve(); return 0; }
對於我個人而言還是比較喜歡樹狀陣列的,因為比較短。當然這也只是個人意見。