【Coel.解題報告】【您好,這裡是CSP-J】[CSP-J 2021] 插入排序
題前閒語
沒什麼想說的,都放在解題思路里面了。
題目大意
題目描述
插入排序是一種非常常見且簡單的排序演算法。小 Z 是一名大一的新生,今天 H 老師剛剛在上課的時候講了插入排序演算法。
假設比較兩個元素的時間為 \(\mathcal O(1)\),則插入排序可以以 \(\mathcal O(n^2)\) 的時間複雜度完成長度為 \(n\) 的陣列的排序。不妨假設這 \(n\) 個數字分別儲存在 \(a_1, a_2, \ldots, a_n\) 之中,則如下虛擬碼給出了插入排序演算法的一種最簡單的實現方式:
這下面是 C/C++ 的示範程式碼(注:為使格式統一,以下程式碼進行了格式調整):
for (int i = 1; i <= n; i++) for (int j = i; j >= 2; j--) if (a[j] < a[j - 1]) { int t = a[j - 1]; a[j - 1] = a[j]; a[j] = t; }
這下面是 Pascal 的示範程式碼:
for i:=1 to n do
for j:=i downto 2 do
if a[j]<a[j-1] then
begin
t:=a[i];
a[i] : = a[j];
a[j] : = t;
end;
為了幫助小 Z 更好的理解插入排序,小 Z 的老師 H 老師留下了這麼一道家庭作業:
H 老師給了一個長度為 \(n\) 的陣列 \(a\),陣列下標從 \(1\)
\(1~x~v\):這是第一種操作,會將 \(a\) 的第 \(x\) 個元素,也就是 \(a_x\) 的值,修改為 \(v\)。保證 \(1 \le x \le n\),\(1 \le v \le 10^9\)。注意這種操作會改變陣列的元素,修改得到的陣列會被保留,也會影響後續的操作。
\(2~x\):這是第二種操作,假設 H 老師按照上面的虛擬碼對 \(a\) 陣列進行排序,你需要告訴 H 老師原來 \(a\) 的第 \(x\) 個元素,也就是 \(a_x\),在排序後的新陣列所處的位置。保證 \(1 \le x \le n\)
H 老師不喜歡過多的修改,所以他保證型別 \(1\) 的操作次數不超過 \(5000\)。
小 Z 沒有學過計算機競賽,因此小 Z 並不會做這道題。他找到了你來幫助他解決這個問題。
輸入輸出格式
輸入格式
第一行,包含兩個正整數 \(n, Q\),表示陣列長度和操作次數。
第二行,包含 \(n\) 個空格分隔的非負整數,其中第 \(i\) 個非負整數表示 \(a_i\)。
接下來 \(Q\) 行,每行 \(2 \sim 3\) 個正整數,表示一次操作,操作格式見【題目描述】。
輸出格式
對於每一次型別為 \(2\) 的詢問,輸出一行一個正整數表示答案。
解題思路
這題是去年\(CSP-J\)的第二題,也是深進第一章習題4。
剛看到這題時我真沒什麼想法,不過仔細一看:
這不就是個資料結構題嗎?
操作1單點修改,操作2查排名,直接上平衡樹!
等等,我們還要注意到插入排序這個先決條件。根據小學知識我們可以知道,插入排序具有穩定性,不會更改相同元素的相對位置。
而直接寫平衡樹是不會有穩定性的,因為權值隨機;但是我們可以人工定義元素的相對位置。
怎麼做呢?比如說對於這麼一個序列(括號為排序前的下標):
我們可以把元素擴大\(n\)倍,在後面加上\(i-1\):
\[12,19,26,33,29,23 \]排序之後就是這樣:
\[12,19,23,26,29,33 \]這與原序列進行插入排序後的位置一致,既可以保證排序後相對位置不變,也可以保證元素排序的正確性。
後面就是平衡樹板子題了,程式碼如下:
#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#define int long long//擴大後元素值可能會超過int
namespace FastIO {
inline int read() {
int x = 0, f = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
f = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
inline void write(int x) {
if (x < 0) {
x = -x;
putchar('-');
}
static int buf[35];
int top = 0;
do {
buf[top++] = x % 10;
x /= 10;
} while (x);
while (top)
putchar(buf[--top] + '0');
puts("");
}
} // namespace FastIO
using namespace std;
using namespace FastIO;
const int maxn = 8e4 + 10, inf = 1e9;
int n, Q, root;
int a[maxn];
struct FHQ_Treap {
int cnt;
int ch[maxn][2], val[maxn], pri[maxn], size[maxn];
inline void pushup(int x) { size[x] = size[ch[x][0]] + size[ch[x][1]] + 1; }
void New_node(int& id, int v) {
size[++cnt] = 1;
val[cnt] = v;
pri[cnt] = rand();
ch[cnt][0] = ch[cnt][1] = 0;
id = cnt;
}
int merge(int x, int y) {
if (x == 0 || y == 0)
return x + y;
if (pri[x] < pri[y]) {
ch[x][1] = merge(ch[x][1], y);
pushup(x);
return x;
} else {
ch[y][0] = merge(x, ch[y][0]);
pushup(y);
return y;
}
}
void split(int id, int k, int& x, int& y) {
if (id == 0)
x = y = 0;
else {
if (val[id] <= k) {
x = id;
split(ch[id][1], k, ch[id][1], y);
pushup(x);
} else {
y = id;
split(ch[id][0], k, x, ch[id][0]);
pushup(y);
}
}
}
inline void insert(int res) {
int x, y, z;
x = y = z = 0;
split(root, res, x, y);
New_node(z, res);
root = merge(merge(x, z), y);
}
inline void erase(int res) {
int x, y, z;
x = y = z = 0;
split(root, res, x, z);
split(x, res - 1, x, y);
y = merge(ch[y][0], ch[y][1]);
root = merge(merge(x, y), z);
}
int Query_Rank(int res) {
int x, y, ans;
split(root, res - 1, x, y);
ans = size[x] + 1;
root = merge(x, y);
return ans;
}
} FHQ_Treap;
signed main() {
n = read(), Q = read();
for (int i = 1; i <= n; i++) {
a[i] = read();
FHQ_Treap.insert(a[i] * n + i - 1);
}
while (Q--) {
int op = read();
if (op == 1) {//修改=刪除+插入
int x = read(), v = read();
FHQ_Treap.erase(a[x] * n + x - 1);
a[x] = v;
FHQ_Treap.insert(a[x] * n + x - 1);
} else {
int x = read();
write(FHQ_Treap.Query_Rank(a[x] * n + x - 1));
}
}
return 0;
}
題後閒話
鬼知道為什麼一道普及-的題目要用平衡樹做(