【Coel.解题报告】【没有忧虑的梦境世界】[HNOI2012]永无乡
,永远的童年,不朽以及避世。
题前碎语
连写了三篇笔记了,写个解题报告调整一下心情。
其实是因为 进阶操作没做完
题目简介
P3224 [HNOI2012]永无乡
洛谷传送门
题目描述
永无乡包含 座岛,编号从 到 ,每座岛都有自己的独一无二的重要度,按照重要度可以将这 座岛排名,名次用 到 来表示。某些岛之间由巨大的桥连接,通过桥可以从一个岛到达另一个岛。如果从岛 出发经过若干座(含 座)桥可以 到达岛 ,则称岛 和岛 是连通的。
现在有两种操作:
B x y
表示在岛 与岛 之间修建一座新桥。
Q x k
表示询问当前与岛 连通的所有岛中第 重要的是哪座岛,即所有与岛 连通的岛中重要度排名第 小的岛是哪座,请你输出那个岛的编号。
输入输出格式
输入格式
第一行是用空格隔开的两个整数,分别表示岛的个数 以及一开始存在的桥数 。
第二行有 个整数,第 个整数表示编号为 的岛屿的排名 。
接下来 行,每行两个整数 ,表示一开始存在一座连接编号为 的岛屿和编号为 的岛屿的桥。
接下来一行有一个整数,表示操作个数 。
接下来 行,每行描述一个操作。每行首先有一个字符 ,表示操作类型,然后有两个整数 。
- 若 为
Q
,则表示询问所有与岛 连通的岛中重要度排名第 小的岛是哪座,请你输出那个岛的编号。 - 若 为
B
,则表示在岛 与岛 之间修建一座新桥。
输出格式
对于每个询问操作都要依次输出一行一个整数,表示所询问岛屿的编号。如果该岛屿不存在,则输出 。
数据规模与约定
- 对于 的数据,保证 , 。
- 对于 的数据,保证 , , 为一个 的排列,,。
解题思路
两件事:查询第 小,连接两个集合。
查询第 小可以用平衡树解决(这里使用 ),连接集合的话就要用并查集了。
注意在使用并查集连接集合的时候,需要进行启发式合并。
启发式合并听起来很高大上,但实际上就是优先把小集合合并给大集合。
这样做有什么意义呢?直觉上告诉我们,大集合合并给小集合是比小集合合并给大集合慢的,所以采取启发式合并可以降低时间复杂度,并且保持答案不变。
在路径压缩和启发式合并的加成下,并查集能够保持十分优秀的复杂度,平均情况下为 ,其中 是阿克曼函数的反函数,增长速度极其缓慢,可以认为是一个不超过 的常数;
而如果没有使用启发式合并,仅使用了路径压缩,极端情况下时间复杂度为 ,效果大打折扣。
介绍了这么多,回归正题吧。你还想起有正题
一些细节:
- 对于每个集合使用一棵平衡树,所以不需要使用;
- 合并集合的时候跑一次 处理两个集合的节点,注意参数的传递与否。
代码如下:
#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;
}
题后闲话
不知道说什么,就放个表情叭(
本文作者:Coel's Blog
本文链接:https://www.cnblogs.com/Coel-Flannette/p/16059224.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步