POJ 2299 線段樹/樹狀數組求逆序數

題意: 給你一組無序數 (小於等於500,000) ,讓你通過相鄰兩數的交換,使其變成升序排列。(這些數兩兩都是不同的)。

剛拿到這道題的時候,不知道該如何下手,後來經過一位善良美麗的學姐的啓發,想到了用逆序數。 那麼什麼是逆序數呢 ? 這在線性代數中有提到過: 在一個排列中,如果一對數的前後位置與大小順序相反,即前面的數大於後面的數,那麼它們就稱爲一個逆序。一個排列中逆序的總數就稱爲這個排列的逆序數。

例如給定一組數 9 1 0 5 4 那麼這組數的逆序數就是 6 。 因爲 9比1大,9比0大,9比5大,9比4大,1比0大,5比4大。 共有6組逆序數。 那麼很顯然,如果某個數之前有 n 個比它大的數,則這 n 個數肯定要經過他被換到後面。 如此操作直到逆序數爲 0 時,排序完成。在這裏就不給出嚴格的數學證明了。

那麼怎麼去求一組數的逆序數呢? 最簡單的方式就是暴力了,逐一排查,不過這樣的時間複雜度爲 O(n^2)對於本題來說肯定要TLE。 那麼我們需要用一個小技巧來解決,本題的技巧就在於用線段樹或者樹狀數組來維護一個區間和來求逆序數。這裏我使用的是樹狀數組,下面我給出詳細的解釋。

思路很簡單,逐一排查要耗費大量的時間,但是如果我們能夠直接知曉某個數身後有多少個比它小的數,我們就只需要對數組進行一次遍歷就能夠獲得整個數列的逆序數了。 我們可以按照小到大的順序將這組數插入樹狀數組中,每次插入的數只需要知道他的身後已經插入了多少個數即可,我們設插入數的座標爲 i ,那麼只需求出(i,n)中有多少個插過的數。在每次插入的時候我們更新當前座標點的數值爲 1 ,表示此座標已經插入了一個數,這樣做便於我們求區間和去統計插入的數的個數。
問題分析至此,我們就可以通過去求出每次插入的元素 i 後(i,n)的區間和來求出這個元素的逆序數。(i,n)的區間和就等於(1,n)的區間和減去(1,i)的區間和。最後,我們累加所有元素的區間和即可得出題解(即需要操作的最少次數)。

代碼如下:

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

///****整個算法的時間複雜度是 O(nlogn)。

const int maxn=5e5+5;
struct Num{          ///儲存數列和數列座標的結構體。
    int zb,v;        ///zb儲存座標,v儲存數列的值。
} p[maxn];
int bit[maxn],n;

int cmp(const Num &a,const Num &b){
    /**
    當兩數相等時將座標小的那個先處理,
    這樣就不會對逆序數的計算產生影響。
    雖然題目說了不會出現重複的數。
    */
    if(a.v == b.v){
        return a.zb < b.zb;
    }
    return a.v < b.v;    ///讓座標隨着值的大小進行從小到大排序。
}

void init(){                  ///多組輸入別忘了初始化。
    memset(p,0,sizeof(p));
    memset(bit,0,sizeof(bit));
}

///********************************樹狀數組維護區間和**********************
int sum(int i){
    int s=0;
    while(i > 0){
        s+=bit[i];
        i-=i&-i;
    }
    return s;
}

void add(int i,int x){
    while(i <= n){
        bit[i]+=x;
        i+=i&-i;
    }
}
///**************************************************************************

int main()
{
    ios::sync_with_stdio(false);
    while(cin>>n&&n){
        init();
        for(int i=1;i<=n;i++){
            cin>>p[i].v;
            p[i].zb=i;
        }
        sort(p+1,p+n+1,cmp);
        long long res=0;             ///res用來記錄逆序數的個數,數據量大要使用long long。
        for(int i=1;i<=n;i++){
            add(p[i].zb,1);          ///座標處更新爲 1 ( Ps :初始化狀態時爲 0)
            res+=sum(n)-sum(p[i].zb);      /// (i+1,n)=(1,n)-(1,i)
        }
        cout<<res<<endl;
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章