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;
	}
posted @ 2018-07-18 08:50  Tony_Double_Sky  阅读(203)  评论(0编辑  收藏  举报