1. 程式人生 > 其它 >【Coel.解題報告】【您好,這裡是CSP-J】[CSP-J 2021] 插入排序

【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\)

開始,並且陣列中的所有元素均為非負整數。小 Z 需要支援在陣列 \(a\) 上的 \(Q\) 次操作,操作共兩種,引數分別如下:

\(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查排名,直接上平衡樹!
等等,我們還要注意到插入排序這個先決條件。
根據小學知識我們可以知道,插入排序具有穩定性,不會更改相同元素的相對位置。
而直接寫平衡樹是不會有穩定性的,因為權值隨機;但是我們可以人工定義元素的相對位置
怎麼做呢?比如說對於這麼一個序列(括號為排序前的下標):

\[2,3,4,5,4,3 \]

我們可以把元素擴大\(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;
}

題後閒話

鬼知道為什麼一道普及-的題目要用平衡樹做(