【Coel.解题报告】【没有忧虑的梦境世界】[HNOI2012]永无乡

\(Never\) \(Land\) ,永远的童年,不朽以及避世。

题前碎语

连写了三篇笔记了,写个解题报告调整一下心情。
其实是因为 \(Splay\) 进阶操作没做完

题目简介

P3224 [HNOI2012]永无乡
洛谷传送门

题目描述

永无乡包含 \(n\) 座岛,编号从 \(1\)\(n\) ,每座岛都有自己的独一无二的重要度,按照重要度可以将这 \(n\) 座岛排名,名次用 \(1\)\(n\) 来表示。某些岛之间由巨大的桥连接,通过桥可以从一个岛到达另一个岛。如果从岛 \(a\) 出发经过若干座(含 \(0\) 座)桥可以 到达岛 \(b\) ,则称岛 \(a\) 和岛 \(b\) 是连通的。

现在有两种操作:

B x y 表示在岛 \(x\) 与岛 \(y\) 之间修建一座新桥。

Q x k 表示询问当前与岛 \(x\) 连通的所有岛中第 \(k\) 重要的是哪座岛,即所有与岛 \(x\) 连通的岛中重要度排名第 \(k\) 小的岛是哪座,请你输出那个岛的编号。

输入输出格式

输入格式

第一行是用空格隔开的两个整数,分别表示岛的个数 \(n\) 以及一开始存在的桥数 \(m\)

第二行有 \(n\) 个整数,第 \(i\) 个整数表示编号为 \(i\) 的岛屿的排名 \(p_i\)

接下来 \(m\) 行,每行两个整数 \(u, v\),表示一开始存在一座连接编号为 \(u\) 的岛屿和编号为 \(v\) 的岛屿的桥。

接下来一行有一个整数,表示操作个数 \(q\)

接下来 \(q\) 行,每行描述一个操作。每行首先有一个字符 \(op\),表示操作类型,然后有两个整数 \(x, y\)

  • \(op\)Q,则表示询问所有与岛 \(x\) 连通的岛中重要度排名第 \(y\) 小的岛是哪座,请你输出那个岛的编号。
  • \(op\)B,则表示在岛 \(x\) 与岛 \(y\) 之间修建一座新桥。

输出格式

对于每个询问操作都要依次输出一行一个整数,表示所询问岛屿的编号。如果该岛屿不存在,则输出 \(-1\)

数据规模与约定

  • 对于 \(20\%\) 的数据,保证 \(n \leq 10^3\), \(q \leq 10^3\)
  • 对于 \(100\%\) 的数据,保证 \(1 \leq m \leq n \leq 10^5\), \(1 \leq q \leq 3 \times 10^5\)\(p_i\) 为一个 \(1 \sim n\) 的排列,\(op \in \{\texttt Q, \texttt B\}\)\(1 \leq u, v, x, y \leq n\)

解题思路

两件事:查询第 \(k\) 小,连接两个集合。
查询第 \(k\) 小可以用平衡树解决(这里使用 \(FHQ-Treap\) ),连接集合的话就要用并查集了。
注意在使用并查集连接集合的时候,需要进行启发式合并
启发式合并听起来很高大上,但实际上就是优先把小集合合并给大集合
这样做有什么意义呢?直觉上告诉我们,大集合合并给小集合是比小集合合并给大集合慢的,所以采取启发式合并可以降低时间复杂度,并且保持答案不变。
在路径压缩和启发式合并的加成下,并查集能够保持十分优秀的复杂度,平均情况下为 \(O(m\alpha(m,n))\) ,其中 \(\alpha(m,n)\) 是阿克曼函数的反函数,增长速度极其缓慢,可以认为是一个不超过 \(4\) 的常数;
而如果没有使用启发式合并,仅使用了路径压缩,极端情况下时间复杂度为 \(O(m\log n)\),效果大打折扣。


介绍了这么多,回归正题吧。你还想起有正题
一些细节:

  • 对于每个集合使用一棵平衡树,所以不需要使用\(root\)
  • 合并集合的时候跑一次 \(dfs\) 处理两个集合的节点,注意参数的传递与否。

代码如下:

#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <iostream>

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("");
}
} 

using std::swap;
using namespace FastIO;

const int maxn = 1e5 + 10;

int n, m, q;

int size[maxn], val[maxn], pri[maxn], ch[maxn][2], herb[maxn];
//herb用来存岛屿编号
int f[maxn];

int find(int x) {//路径压缩
    return x == f[x] ? x : f[x] = find(f[x]);
}

void pushup(int x) {
    size[x] = size[ch[x][0]] + size[ch[x][1]] + 1;
}

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

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 insert(int& id, int x) {
    int y, z, v = val[x];
    split(id, v, y, z);
    id = merge(merge(y, x), z);
}

void dfs(int x, int& y) {
    if (x == 0)
        return;
    dfs(ch[x][0], y);
    dfs(ch[x][1], y);
    ch[x][0] = ch[x][1] = 0;
    insert(y, x);
}

int Heuristic_Merge(int x, int y) {
    if (size[x] > size[y])
        swap(x, y);//启发式合并的精髓
    dfs(x, y);
    return y;
}

void uni(int u, int v) {
    int x = find(u), y = find(v), z;
    if (x == y)
        return;
    z = Heuristic_Merge(f[u], f[v]);
    f[x] = f[y] = f[z] = z;
}

int Query_kth(int id, int k) {
    while (1) {
        if (k <= size[ch[id][0]])
            id = ch[id][0];
        else if (k == size[ch[id][0]] + 1)
            return id;
        else {
            k -= size[ch[id][0]] + 1;
            id = ch[id][1];
        }
    }
}

int main() {
    srand(3224);
    n = read(), m = read();
    for (int i = 1; i <= n; i++) {
        f[i] = i;
        val[i] = read();
        pri[i] = rand();
        size[i] = 1;
        herb[val[i]] = i;
    }
    for (int i = 1; i <= m; i++) {
        int u = read(), v = read();
        uni(u, v);
    }
    q = read();
    while (q--) {
        char op[5];
        scanf("%s", op + 1);//受cirno教导用字符串输入操作符
        if (op[1] == 'B') {
            int u = read(), v = read();
            uni(u, v);
        } else {
            int x = read(), k = read();
            if (size[find(x)] < k)
                puts("-1");
            else
                write(herb[val[Query_kth(find(x), k)]]);
        }
    }
    return 0;
}

题后闲话

不知道说什么,就放个表情叭(

posted @ 2022-03-26 16:57  秋泉こあい  阅读(26)  评论(0编辑  收藏  举报