[HNOI2012]永无乡

题目:BZOJ2733、洛谷P3324、codevs1477

题目大意:给你n座岛,每座岛一个权值,某些岛是连在一起的。

有q个操作,每个操作可以:

1.将两座岛连起来。

2.输出和x岛直接或间接连接的所有岛里,权值第y小的岛的编号。

解题思路:线段树合并。这是我写的第一道线段树合并题。听说可以用高大上的“splay启发式合并”写,然而我并不会。orz

动态开点,给线段树留的内存不能太小,否则会炸掉。

C++ Code:

 

#include<cstdio>
using namespace std;
int n,m,fa[100005],v[100005],num[100005],root[100005],node=0,d[100005*22],ld[100005*22],rd[100005*22];
int dad(int x){return fa[x]==x?x:fa[x]=dad(fa[x]);}
void make(int& k,int l,int r,int p){
	if(!k)k=++node;
	if(l==r){
		d[k]=1;return;
	}
	int m=(l+r)>>1;
	if(p<=m)make(ld[k],l,m,p);else
	make(rd[k],m+1,r,p);
	d[k]=d[ld[k]]+d[rd[k]];
}
int merge(int x,int y){
	if(!x||!y)return x^y;
	ld[x]=merge(ld[x],ld[y]);
	rd[x]=merge(rd[x],rd[y]);
	d[x]=d[ld[x]]+d[rd[x]];
	return x;
}
int query(int k,int l,int r,int p){
	if(l==r)return l;
	int m=(l+r)>>1;
	if(d[ld[k]]>=p)return query(ld[k],l,m,p);
	return query(rd[k],m+1,r,p-d[ld[k]]);
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&v[i]);
		fa[i]=i;
		num[v[i]]=i;
	}
	while(m--){
		int x,y;
		scanf("%d%d",&x,&y);
		int a=dad(x),b=dad(y);
		fa[b]=a;
	}
	for(int i=1;i<=n;i++){
		make(root[dad(i)],1,n,v[i]);
	}
	char c[3];
	scanf("%d",&m);
	while(m--){
		int x,y;
		scanf("%s%d%d",c,&x,&y);
		if(c[0]=='B'){
			int p=dad(x),q=dad(y);
			if(p!=q){
				fa[q]=p;
				root[q]=merge(root[p],root[q]);
			}
		}else{
			int p=dad(x);
			if(d[root[p]]<y)puts("-1");else
			printf("%d
",num[query(root[p],1,n,y)]);
		}
	}
	return 0;
}
原文地址:https://www.cnblogs.com/Mrsrz/p/7150355.html