10.12 模擬賽

2huk發表於2024-10-12

題解

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];
}

image-20241012163924009

所以我們需要維護一種資料結構,支援查詢某種數字在序列中第一次和第二次的出現位置,以及刪除第一個數。雙端佇列(deque)或連結串列(list)即可。

加上樹狀陣列總複雜度 \(\mathcal O(n \log n)\)