题目:
题目链接:[LUOGU [HNOI2012] 永无乡]
题解:
这个题,就是让求在同一连通图中的区间第k小,那怎么处理连通图的问题呢,很显然,并查集啊,,,那怎么处理区间第k小呢,这点就可以写一个线段树合并了,在每个点上建个权值线段树,这道题中就把权值线段树设为每个区间的排名即可,然后在累加。
代码:
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int s=0,w=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')w=-1;ch=getchar();}
while(ch<='9'&&ch>='0')s=s*10+ch-'0',ch=getchar();
return s*w;
}
const int sea=1e5+7;
struct hit{int ls,rs,sum,id;}tr[sea*40];//sum这里是排名
int n,m,q,seg=0,ans,fa[sea],root[sea],v[sea];
int get(int x){if(x==fa[x]) return x;else return fa[x]=get(fa[x]);}
void build(int &k,int l,int r,int val,int tt)
{
if(!k) k=++seg;
if(l==r){tr[k].id=tt,tr[k].sum++; return ;}
int mid=(l+r)/2;
if(val<=mid) build(tr[k].ls,l,mid,val,tt);
else build(tr[k].rs,mid+1,r,val,tt);
tr[k].sum=tr[tr[k].ls].sum+tr[tr[k].rs].sum;
}
int incor(int x,int y,int l,int r)
{
if(!x) return y;if(!y) return x;
if(l==r){if(tr[y].id)tr[x].sum+=tr[y].sum,tr[x].id=tr[y].id;return x;}
int mid=(l+r)/2;
tr[x].ls=incor(tr[x].ls,tr[y].ls,l,mid);
tr[x].rs=incor(tr[x].rs,tr[y].rs,mid+1,r);
tr[x].sum=tr[tr[x].ls].sum+tr[tr[x].rs].sum;
return x;
}
int ask(int x,int k,int l,int r)
{
int s=0;
if(k>tr[x].sum||!k) return 0;
if(l==r) return tr[x].id;
int mid=(l+r)/2;
if(k<=tr[tr[x].ls].sum) s=ask(tr[x].ls,k,l,mid);
else s=ask(tr[x].rs,k-tr[tr[x].ls].sum,mid+1,r);
//找第k小的数时,如果左儿子的数超过k个就在左儿子里,否则去右儿子找第k-tr[tr[x].ls].sum的数,因为每个子树里都存的是在本子树中的排名
return s;
}
int main()
{
n=read(); m=read();
for(int i=1;i<=n;i++) fa[i]=i,root[i]=i,v[i]=read(); seg=n;
for(int i=1;i<=n;i++) build(root[i],1,n,v[i],i);
for(int i=1,x,y;i<=m;i++) x=read(),y=read(),x=get(x),y=get(y),fa[y]=x,
incor(root[x],root[y],1,n);
q=read();
for(int i=1;i<=q;i++)
{
char ch; cin>>ch;
int x=read(),y=read();
if(ch=='B')
{
x=get(x),y=get(y);
if(x==y) continue;
fa[y]=x,incor(root[x],root[y],1,n);
}
else
{
x=get(x);
int ans=ask(x,y,1,n);
if(!ans) puts("-1"); else printf("%d\n",ans);
}
}
return 0;
}