歸併排序求逆序對
阿新 • • 發佈:2020-08-16
歸併排序求逆序對
逆序對:對於數列的第 \(i\) 個和第 \(j\) 個元素,如果滿足 \(i < j\) 且 \(a[i] > a[j]\),則其為一個逆序對。
暴力求法就是兩遍for迴圈,\(O(n^2)\)超時
用歸併可以順便求出來,\(O(nlogn)\)
在一次歸併排序中,因為歸併要求將一個完整區間分為兩塊,所以逆序對出現了三種情況。
- 逆序對在mid的左邊
- 逆序對在mid的右邊
- 逆序對的兩個元素一個在mid左邊,一個在mid右邊
之前提過歸併可以順便求出來,這裡設歸併排序有大小為該區間逆序對個數的返回值,即\(l-r的逆序對個數=merge\_sort(l,r)\)
因為是遞迴處理嘛,所以先將左右兩邊的歸併排序。重點是第三部分,如何處理這種情況。
首先,通過前兩次歸併排序,從\(L-mid,mid+1-R\)兩個部分都是有序的,在合併這兩個有序序列時,可以求出第三種情況的逆序對個數。
重新想一下逆序對的定義,是在前面的數比後面的數大。在區間上來看,就是\(L-mid\)的這一部分中有元素比\(mid+1-R\)中的元素大。
每次移動右半邊的指標時,從中間到左半邊指標所包含的所有數字 都是大於右半邊指標所指向的數字的,這些都可以組成逆序對,直接加上即可。
最後呢,其實1和2的情況沒有討論,因為歸併排序最後還是要分到只有兩個數進行排序的。一個數不能構成逆序對,所以只有兩個數字的情況下,只存在第三種情況,接著往下遞迴就不需要考慮12了。
程式碼:
#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef unsigned long long ull; const int maxn = 1e5 + 10; int n, q[maxn], tmp[maxn]; ll solve(int l, int r) { if (l >= r) return 0; int mid = l + r >> 1; ll ans = solve(l, mid) + solve(mid + 1, r); int i = l, j = mid + 1, k = l; while (i <= mid && j <= r) { if (q[i] <= q[j])//特別要注意這個,雖然加不加等號對於排序而言並沒有影響,但是求逆序數時,加不加等於號是兩種情況。只有在嚴格大於的時候才是逆序數。 tmp[k++] = q[i++]; else { tmp[k++] = q[j++]; ans += (mid - i + 1); } } while (i <= mid) tmp[k++] = q[i++]; while (j <= r) tmp[k++] = q[j++]; for (int i = l; i <= r; i++) q[i] = tmp[i]; return ans; } int main() { scanf("%d", &n); for (int i = 0; i < n; ++i) scanf("%d", &q[i]); ll res = solve(0, n - 1); cout << res; }