题目: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\n",num[query(root[p],1,n,y)]);
}
}
return 0;
}