题意
给定一幅图,图上有 n 个节点,一开始有
m 条边
每个节点有一个重要度排名: Wi,Wi∈【1,n】
现在有两种操作:
①.连接 u 和v
②.询问与 v 联通的点集中,重要度第k 小的点的编号
非强制在线
解法
线段树(主席树)+启发式合并:
这道题和 【bzoj3545】Peaks 类似,只是没有边权,然后是求第 k 小
首先,我们考虑用并查集来维护是否联通,这很容易就能做到
其次,求第k 小的数可以用主席树来进行,我们对于每一个点建立一棵值域线段树,范围是 【1,n】 ,因为所有的值域都相同,因此可以使用启发式合并
对于连接操作,首先判断 u 和v 是否在同一个联通块内,如果不在,我们就需要合并 u 和v 所在的联通块的值域线段树,然后将 u 和v 加入同一个联通块内(这里可以使用并查集的按秩合并,这样会快一点)
在询问时,因为我们询问出的是一个权值,即重要度排名,而题目要求的是点的编号,所以我们要用一个 rank 数组来记录每一个重要度排名对应的点的编号
复杂度
O(nlog2n)
代码
#include<iostream>
#include<cstdlib>
#include<cstdio>
#define mid (l+r)/2
using namespace std;
const int MAXN=100010;
struct node
{
int u,v;
void gi() { scanf("%d%d",&u,&v); }
}t[MAXN];
int head[MAXN],num;
int ls[MAXN*20],rs[MAXN*20];
int rt[MAXN],T[MAXN*20];
int w[MAXN],f[MAXN];
int rk[MAXN];
int n,m,q,cnt;
int find(int x)
{
if( x!=f[x] ) f[x]=find( f[x] );
return f[x];
}
void insert(int &k,int l,int r,int x)
{
if( !k ) k=++cnt;
if( l==r ) { T[k]=1;return ; }
if( x<=mid ) insert( ls[k],l,mid,x );
else insert( rs[k],mid+1,r,x );
T[k]=T[ls[k]]+T[rs[k]];
}
int query(int k,int l,int r,int x)
{
if( l==r ) return l;
if( T[ls[k]]>=x ) return query( ls[k],l,mid,x );
else return query( rs[k],mid+1,r,x-T[ls[k]] );
}
int merge(int x,int y)
{
if( !x || !y ) return x+y;
ls[x]=merge( ls[x],ls[y] );
rs[x]=merge( rs[x],rs[y] );
T[x]=T[ls[x]]+T[rs[x]];
return x;
}
void Union(int u,int v)
{
u=find( u ),v=find( v );
if( u==v ) return ;
rt[u]=merge( rt[u],rt[v] );
f[v]=u;
}
int main()
{
char s[30];
int tmp,u,v;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&w[i]);
f[i]=i,rk[w[i]]=i;
}
for(int i=1;i<=n;i++) insert( rt[i],1,n,w[i] );
for(int i=1;i<=m;i++) scanf("%d%d",&u,&v),Union( u,v );
scanf("%d",&q);
for(int i=1;i<=q;i++)
{
scanf("%s",s);
scanf("%d%d",&u,&v);
if( s[0]=='B' ) Union( u,v );
else
{
tmp=rt[find( u )];
if( T[tmp]<v ) printf("%d\n",-1);
else printf("%d\n",rk[query( tmp,1,n,v )]);
}
}
return 0;
}