題解
A. 選擇排序
粘過來題面的程式碼:
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++)
if (a[i] < a[j])
swap(a[i], a[j]);
}
考慮如何計算整個串的答案。
首先暴力做一遍 \(i = 1\)。此時序列中的最大值一定會被交換到 \(a_1\)。
然後將剛才的答案加上目前 \(a\) 中的逆序對數量就是真實答案。具體的,我們需要對於每個 \(j\),計算 \(j\) 之前有多少種數 \(> a_j\)。
為啥?模擬+找規律。
for (int x = 1; x <= n; ++ x ) {
for (int i = 1; i <= x; ++ i ) a[i] = b[i];
int res = 0;
for (int j = 1; j <= x; ++ j ) {
if (a[1] < a[j]) {
swap(a[1], a[j]);
res ++ ;
}
}
for (int i = 2; i <= x; ++ i ) res += /* i 前面有多少種數 > a[i] */;
cout << res << " \n"[x == n];
}
用樹狀陣列實現可以做到整體 \(\mathcal O(n^2)\) 的複雜度。現在考慮最佳化上面的程式碼。
for (int x = 1; x <= n; ++ x ) {
for (int i = 1; i <= x; ++ i ) a[i] = b[i];
int res = 0;
// 最佳化這個迴圈比較簡單。注意到從 x -> x+1 時,我們只需要在上一次的基礎上多做一次 j = x+1 即可。
for (int j = 1; j <= x; ++ j ) {
if (a[1] < a[j]) {
swap(a[1], a[j]);
res ++ ;
}
}
// 也就是說,從 x -> x+1 時 a[1 ~ x] 中至多隻會有 a[1] 會發生變化,其他位置一定不會發生改變
// 別忘了每次上面的迴圈結束後,a[1] 都是目前的最大值
// 我們知道當列舉的是 x 時 a[1] 是最大值。那麼當列舉的變成 x+1 時,如果上面 j = x+1 的沒有執行(即 a[x + 1] < a[1],即 a[1] 仍然是最大值),那麼前面所有數對答案的貢獻都沒有發生改變。
// 否則,由於 a[1] 可能會出現多次,考慮下面的圖。先我們即將將第一個位置從 a[1] 變成 a[x + 1],且 a[x + 1] > a[i]
// 顯然綠色區間內的數對答案的貢獻沒有變化。因為 a_1 本來就比它們大,而我們是把 a_1 替換成一個更大的數。
// 但是藍色區間內的數對答案的貢獻會增加 1。這是因為原先它們前面有兩個 a_1,但是我們只計算了一個的貢獻。現在我們把其中一個 a_1 改變了,也就是說原來的兩個數現在都要貢獻一次,所以這些數的貢獻會大 1。
for (int i = 2; i <= x; ++ i ) res += /* i 前面有多少種數 > a[i] */;
cout << res << " \n"[x == n];
}
所以我們需要維護一種資料結構,支援查詢某種數字在序列中第一次和第二次的出現位置,以及刪除第一個數。雙端佇列(deque)或連結串列(list)即可。
加上樹狀陣列總複雜度 \(\mathcal O(n \log n)\)。