【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 = 1.3e4 + 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;
}

上面有一个地方提到了数据范围,这里深入讲一下。
一开始给出的序列长度为 \(8000\),所以肯定要开到这么多。
与此同时, FHQ_Treap 的修改操作是先插入再删除,所以要开的数组还得再加上删除操作的数量。
题目中给出保证类型 \(1\) 的操作次数不超过 \(5000\), 因此总数组大小为 \(13000\)

题后闲话

鬼知道为什么一道普及-的题目要用平衡树做(

posted @ 2022-04-04 14:17  秋泉こあい  阅读(102)  评论(0编辑  收藏  举报