【洛谷P3224】永无乡 并查集+Splay启发式合并

题目大意:给定 N 个点的图,点有点权,初始有一些无向边,现在有 Q 个询问,每个询问支持动态增加一条无向边连接两个不连通的点和查询第 X 个点所在的联通块中权值第 K 大的是哪个点。

题解:学会了平衡树的启发式合并。
以每个点建立一棵平衡树,需要加边时则合并两个点对应的平衡树,启发式的思想在于合并的时候将 size 小的平衡树信息合并到 size 大的树上。这样,对于每一个被合并的点来说,其所在的平衡树的大小必定翻倍,而最极端的话,所有的点都在一个平衡树中,size = n,因此,每个点被合并的次数不超过 \(O(logn)\) 次,即:新建的节点数不超过 \(O(nlogn)\) 个。
需要注意的是,对于 Splay 来说,若对于以前的单个平衡树来说,根节点的父节点可以取 0,但是有 N 棵平衡树的话,每棵平衡树根节点的父节点不能相同,因此,将 tot 初始化成 N,对于第 i 个平衡树的根节点,将 i 作为其父节点,并且根节点需要手动初始化,不能 insert。
连通性什么的直接并查集维护就好喽。

代码如下

#include <bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;

int n,m,q,f[maxn],mp[maxn];
int find(int x){return x==f[x]?x:f[x]=find(f[x]);}
struct node{
	#define ls(x) t[x].ch[0]
	#define rs(x) t[x].ch[1]
	int ch[2],fa,val,size;
}t[maxn<<3];
int tot,root[maxn];
inline void pushup(int o){t[o].size=t[ls(o)].size+t[rs(o)].size+1;}
inline bool get(int o){return o==rs(t[o].fa);}
inline void rotate(int o){
	int fa=t[o].fa,gfa=t[fa].fa;
	int d1=get(o),d2=get(fa);
	t[fa].ch[d1]=t[o].ch[d1^1],t[t[o].ch[d1^1]].fa=fa;
	t[fa].fa=o,t[o].ch[d1^1]=fa;
	t[gfa].ch[d2]=o,t[o].fa=gfa;
	pushup(fa),pushup(o);
}
inline void splay(int o,int goal){
	while(t[o].fa!=goal){
		int fa=t[o].fa,gfa=t[fa].fa;
		if(gfa!=goal)get(o)==get(fa)?rotate(fa):rotate(o);
		rotate(o);
	}
	if(goal<=n)root[goal]=o;
}
void insert(int val,int i){
	int o=root[i],fa=0;
	while(o)fa=o,o=t[o].ch[t[o].val<val];
	o=++tot;
	if(fa)t[fa].ch[t[fa].val<val]=o;
	t[o].fa=fa,t[o].val=val,t[o].size=1;
	splay(o,i);
}
int kth(int o,int k){
	if(k<=t[ls(o)].size)return kth(ls(o),k);
	else if(k>t[ls(o)].size+1)return kth(rs(o),k-t[ls(o)].size-1);
	else return t[o].val;
}
void dfs(int o,int i){
	if(ls(o))dfs(ls(o),i);
	if(rs(o))dfs(rs(o),i);
	insert(t[o].val,i);
}
void merge(int a,int b){
	if(!a||!b)return;
	int x=find(a),y=find(b);
	if(x==y)return;
	if(t[root[x]].size>t[root[y]].size)swap(x,y);
	f[x]=y;
	dfs(root[x],y);
}

void read_and_parse(){
	scanf("%d%d",&n,&m),tot=n;
	for(int i=1,val;i<=n;i++){
		scanf("%d",&val);
		f[i]=i,mp[val]=i;
		root[i]=++tot,t[root[i]].fa=i,t[root[i]].size=1,t[root[i]].val=val;
	}
	for(int i=1,a,b;i<=m;i++){
		scanf("%d%d",&a,&b);
		merge(a,b);
	}
}

void solve(){
	char opt[2];int x,y;
	scanf("%d",&q);
	while(q--){
		scanf("%s%d%d",opt,&x,&y);
		if(opt[0]=='B')merge(x,y);
		else{
			if(y>t[root[find(x)]].size)puts("-1");
			else printf("%d\n",mp[kth(root[find(x)],y)]);
		}
	}
}

int main(){
	read_and_parse();
	solve();
	return 0;
}
posted @ 2019-03-03 18:38  shellpicker  阅读(214)  评论(0编辑  收藏  举报