测试地址:永无乡
做法:本题需要用到平衡树启发式合并。
题目要维护每个连通块的第大,并且要支持合并。维护第大我们知道可以用平衡树解决,但是平衡树的合并我们好像除了暴力就没有想法了。怎么样比较快地合并两棵平衡树?想法上非常简单,我们只需要暴力把点数比较小的那棵平衡树上的点一一插入到另一棵平衡树上。为什么这样就更优了呢?因为每次合并,新平衡树的大小必然是原小平衡树的两倍以上,那么每个元素就最多会被插入次,所以总的复杂度就是的,可以承受。
好像根据某个定理,按小平衡树的中序遍历插入好像能达到……虽然好像感觉没有变快,但应该还是能优化些常数的。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
int n,m,q;
int top[100010],fa[100010],ch[100010][2],key[100010],siz[100010];
void pushup(int x)
{
siz[x]=siz[ch[x][0]]+siz[ch[x][1]]+1;
}
void rotate(int x,bool f)
{
int y=fa[x];
ch[y][!f]=ch[x][f];
fa[ch[x][f]]=y;
if (fa[y]) ch[fa[y]][ch[fa[y]][1]==y]=x;
fa[x]=fa[y];
fa[y]=x;
ch[x][f]=y;
pushup(y),pushup(x);
}
void Splay(int x,int &rt,int goal)
{
while(fa[x]!=goal)
{
if (fa[fa[x]]==goal) rotate(x,ch[fa[x]][0]==x);
else
{
int y=fa[x],z=fa[fa[x]];
bool f=(ch[y][1]==x);
if (ch[z][f]==y) rotate(y,!f),rotate(x,!f);
else rotate(x,!f),rotate(x,f);
}
}
if (goal==0) rt=x;
}
int find_kth(int x,int k)
{
int rt;
Splay(x,rt,0);
if (siz[x]<k) return -1;
while(siz[ch[x][0]]+1!=k)
{
int s=siz[ch[x][0]]+1;
if (k<s) x=ch[x][0];
else x=ch[x][1],k-=s;
}
Splay(x,rt,0);
return x;
}
void insert(int &v,int &rt,int x,int f)
{
if (!v)
{
v=x;
ch[v][0]=ch[v][1]=0;
fa[v]=f;
Splay(v,rt,0);
return;
}
insert(ch[v][key[x]>key[v]],rt,x,v);
}
int find(int x)
{
int r=x,i=x,j;
while(r!=top[r]) r=top[r];
while(i!=r) {j=top[i],top[i]=r,i=j;}
return r;
}
void order(int v,int &rty)
{
if (ch[v][0]) order(ch[v][0],rty);
if (ch[v][1]) order(ch[v][1],rty);
insert(rty,rty,v,0);
}
void merge(int x,int y)
{
int fx=find(x),fy=find(y),rt;
top[fy]=fx;
Splay(x,rt,0),Splay(y,rt,0);
if (siz[x]>siz[y]) swap(x,y);
order(x,y);
}
int main()
{
scanf("%d%d",&n,&m);
siz[0]=0;
for(int i=1;i<=n;i++)
{
top[i]=i;
fa[i]=ch[i][0]=ch[i][1]=0;
siz[i]=1;
scanf("%d",&key[i]);
}
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
if (find(x)!=find(y)) merge(x,y);
}
scanf("%d",&q);
while(q--)
{
char op[5];
int x,y;
scanf("%s%d%d",op,&x,&y);
if (op[0]=='B'&&find(x)!=find(y)) merge(x,y);
if (op[0]=='Q') printf("%d
",find_kth(x,y));
}
return 0;
}