P3224 [HNOI2012]永无乡
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 个数,依次描述从岛 1 到岛 n 的重要度排名。随后的 m 行每行是用空格隔开的两个正整数 a_i和 b_i,表示一开始就存在一座连接岛 a_i和岛 b_i 的桥。
后面剩下的部分描述操作,该部分的第一行是一个正整数 q ,表示一共有 q 个操作,接下来的 q 行依次描述每个操作,操作的 格式如上所述,以大写字母 Q 或 B 开始,后面跟两个不超过 n 的正整数,字母与数字以及两个数字之间用空格隔开。
输出格式:
对于每个 Q x k 操作都要依次输出一行,其中包含一个整数,表示所询问岛屿的编号。如果该岛屿不存在,则输出 −1 。
\(Treap\) 的启发式合并。
什么是启发式合并呢? 说得很高大上, 其实就是把 \(size\) 小的往 \(size\) 大的里丢。。
这里提供两种方案, 第一种, 遍历 \(size\) 小的 \(Treap\), 将其节点作为一个新节点, 重新加入大的树, 也就是说, 丢弃之前的节点, 重新插入。 最坏的情况,每次以 \(2\) 倍合并(像 \(2048\) 那样), 最坏情况空间为 \(2N\) 。
我采用的是将节点再加入新树的方式。 合并的时候直接用已有的编号, 等于回收了空间, 空间占用较小可是略显复杂
如何遍历一颗 \(Treap\) ?
采用 \(dfs\) 的方式。每个点只有两个儿子, \(dfs\) 很好写。不过有些细节需要注意, 我将节点新的初始化写在了 \(dfs\) 里, 比如在加入新树之前要将此节点的儿子清空(因为重要度不重复, 加入新树后必定是叶子节点), 防止出现一些 玄学 错误
void add(int &id, int p, int v){
if(!id){id = p;pushup(id);return ;}
int d = v < val[id] ? 0 : 1;
add(ch[id][d], p, v);
if(dat[ch[id][d]] > dat[id])Rotate(id, d ^ 1);
pushup(id);
}
void dfs(int id, int &root){//注意引用来修改入口的根节点
int v1 = ch[id][0], v2 = ch[id][1];
ch[id][0] = ch[id][1] = 0;
size[id] = 1;
add(root, id, val[id]);
if(v1)dfs(v1, root);
if(v2)dfs(v2, root);
}
如何开很多个 \(Treap\) 而不爆空间
且维护节点之间的关系?
维护节点之间的关系使用并查集即可。
刚开始想的是开 \(N\) 个 满大小的 \(Treap\) ,发现肯定会爆空间, 于是乎开每个点即可, 这里的 \(ch[maxn][2]\) 代表的就不是一颗 \(Treap\) 了, 而是一片森林中父亲与儿子的关系。 因为我们可以回收空间, \(ch\) 数组大小 开一倍即可。 因为有多颗树, 我们开一个 \(root[\ ]\) 表示某个节点的根节点, 考虑使用并查集维护即可。
int father[maxn];
int findfather(int v){
if(father[v] == v)return v;
return father[v] = findfather(father[v]);
}
//---------------------------------------------------------------------
int num, ne, na;
void dfs(int id, int &root){
int v1 = ch[id][0], v2 = ch[id][1];
ch[id][0] = ch[id][1] = 0;
size[id] = 1;
add(root, id, val[id]);
if(v1)dfs(v1, root);
if(v2)dfs(v2, root);
}
void merge(int a, int b){
int faA = findfather(a), faB = findfather(b);
if(faA == faB)return ;//把A加入B
if(size[root[faA]] > size[root[faB]])swap(faA, faB);
dfs(root[faA], root[faB]);
father[faA] = faB;
}
Code
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
int RD(){
int flag = 1, out = 0;char c = getchar();
while(c < '0' || c > '9'){if(c == '-')flag = -1;c = getchar();}
while(c >= '0' && c <= '9'){out = out * 10 + c - '0';c = getchar();}
return flag * out;
}
const int maxn = 1000019, INF = 1e9 + 19;
int ch[maxn][2];
int size[maxn];
int val[maxn], dat[maxn];
int root[maxn], tot;
int New(int v){
val[++tot] = v;dat[tot] = rand();
size[tot] = 1;
return tot;
}
void pushup(int id){size[id] = size[ch[id][0]] + size[ch[id][1]] + 1;}
void Rotate(int &id, int d){
int temp = ch[id][d ^ 1];
ch[id][d ^ 1] = ch[temp][d];
ch[temp][d] = id;
id = temp;
pushup(ch[id][d]), pushup(id);
}
int get_val(int id, int rank){
if(!id)return 0;
if(size[ch[id][0]] >= rank)return get_val(ch[id][0], rank);
else if(size[ch[id][0]] + 1 >= rank)return id;
else return get_val(ch[id][1], rank - size[ch[id][0]] - 1);
}
void add(int &id, int p, int v){
if(!id){id = p;pushup(id);return ;}
int d = v < val[id] ? 0 : 1;
add(ch[id][d], p, v);
if(dat[ch[id][d]] > dat[id])Rotate(id, d ^ 1);
pushup(id);
}
//--------------------------------------------------------------------
int father[maxn];
int findfather(int v){
if(father[v] == v)return v;
return father[v] = findfather(father[v]);
}
//---------------------------------------------------------------------
int num, ne, na;
void dfs(int id, int &root){
int v1 = ch[id][0], v2 = ch[id][1];
ch[id][0] = ch[id][1] = 0;
size[id] = 1;
add(root, id, val[id]);
if(v1)dfs(v1, root);
if(v2)dfs(v2, root);
}
void merge(int a, int b){
int faA = findfather(a), faB = findfather(b);
if(faA == faB)return ;//把A加入B
if(size[root[faA]] > size[root[faB]])swap(faA, faB);
dfs(root[faA], root[faB]);
father[faA] = faB;
}
int main(){
num = RD(); ne = RD();
for(int i = 1;i <= num;i++)father[i] = i;
for(int i = 1;i <= num;i++)root[findfather(i)] = New(RD());
for(int i = 1;i <= ne;i++){
int u = RD(), v = RD();
merge(u, v);
}
na = RD();
char cmd;
for(int i = 1;i <= na;i++){
cin>>cmd;
if(cmd == 'Q'){
int x = RD(), k = RD();
int rt = root[findfather(x)];
if(size[rt] < k)printf("-1\n");
else printf("%d\n", get_val(rt, k));
}
else{
int a = RD(), b = RD();
merge(a, b);
}
}
return 0;
}