1. 程式人生 > >逆序對的三種求法(歸併排序,樹狀陣列,線段樹)

逆序對的三種求法(歸併排序,樹狀陣列,線段樹)

求逆序對個數的三種方法

逆序對: 對於一個序列 $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;
}

對於我個人而言還是比較喜歡樹狀陣列的,因為比較短。當然這也只是個人意見。