1. 程式人生 > >逆序對的兩種求法(復習)

逆序對的兩種求法(復習)

思想 scan 個數 code 時間 clu ast void 逆序

逆序對

對於一個數列\(a_1...a_n\),定義一有序對\((i,j)\)當且僅當\(i<j\)\(a_i>a_j\)為逆序對。接著我們來考慮怎麽求

*1. 歸並排序

回顧歸並排序的過程,將當且的數列\([l,r]\)分成兩個長度相等的部分\([l,mid]\)\([mid+1,r]\),分治下去排序,每次合並的代價是區間的長度,所以得到時間復雜度為:
\[ T(n)=2T(\frac{n}{2})+O(n) \]
根據\(master\)定理可知時間復雜度為\(\Theta(nlog_2n)\)

int MergeSort (int l, int r) {
    if (l == r) return ;
    int mid = (l + r) >> 1;
    MergeSort(l, mid), MergeSort(mid + 1, r);
    int i = l, j = mid + 1, t = l;
    while(i <= mid && j <= r)
        if (a[i] <= a[j]) b[t++] = a[i++];
        else b[t++] = a[j++];
    while(i <= mid) b[t++] = a[i++]; while(j <= r) b[t++] = a[j++];
    for(int k = l; k <= r; ++k) a[k] = b[k];
}

等一下!你講了這麽久,到底怎麽求逆序對???


我們截取一段代碼:

else b[t++] = a[j++]

由於歸並排序是將一個區間分成左右兩端,故右邊一段中的任何一個元素一定在左邊一段中任何一個元素之後,這種性質同樣用於分治處理,所以,當我們加入一個右區間元素時,此時左區間中剩下沒加入的元素一定就比它大,所以改寫一下:

else b[t++] = a[j++], ans += mid - i + 1;//ans為逆序對個數

由於分治的性質,所以每個逆序對都會不重不漏地選到。

*2. 樹狀數組

\(BIT\)作為一種巧妙的數據結構,其利用了一種類似於前綴和的思想。通過權值\(BIT\)來求解逆序對。

其算法核心在於,先確立\(a_j<a_i\)的大小關系,按從小到大插入其下標,通過計算前綴和的方式來求解。

比如我們現在有一個數\(a_i\),我們向\(BIT\)中插入其下標\(i\)後計算當前小於等於其下標的數的個數\(tot\),則答案就是\(i-tot\)

因為此時\(a_i>a_j\)(\(j\)為之前已插入的數的下標),所以\(i-tot\)即位置在\(i\)之後的個數就是當前\(i\) 對答案產生的貢獻(滿足\(a_j<a_i\&j>i\))。

#include <cstdio>
#include <algorithm>
typedef long long ll;
using std::sort;

const ll N = 5e5 + 10;
ll n, a[N], b[N], c[N], ans;

inline bool cmp (ll x, ll y) { return a[x] < a[y] || (a[x] == a[y] && x < y); }
inline ll lowbit (ll x) { return x & (-x); }
inline void add (ll x, ll y) { for (; x <= n; x += lowbit(x)) c[x] += y; }
inline ll query (ll x) { ll y = 0; for (; x > 0; x -= lowbit(x)) y += c[x]; return y; }

int main () {
    scanf ("%lld", &n);
    for (ll i = 1; i <= n; ++i) scanf ("%lld", a + i), b[i] = i;
    sort (b + 1, b + n + 1, cmp);
    for (ll i = 1; i <= n; ++i) add(b[i], 1). ans += i - query (b[i]);
    printf ("%lld\n", ans);
    return 0;
} 

逆序對的兩種求法(復習)