[BZOJ2733]HNOI2012永无乡|平衡树启发式合并

时间:2022-12-16 13:36:34

一看题吓傻了根本不会做啊。。然后又看了几遍题,好像就是维护各个联通块的k大嘛。。一开始纠结了好久,后来想起好像没有断边的操作,那就好搞了。。第一次写splay的启发式合并,用splay维护每个联通块的重要度序列,然后可以弄个并查集,如果连边的时候是两个不同联通块,那就把size小的那个splay按中序遍历一个一个插入到size大的那个splay里去,这就是启发式合并。。询问就按findkth那样搞就行了。。

#include<cstdio>
#include<iostream>
#define N 100005
#define inf 999999999
#define ll long long
using namespace std;
int n,m,x,y,q,top,i,c[N][2],pre[N],fa[N],size[N],st[N];
ll v[N];
char s[2];
int getfa(int x){return fa[x]==x?x:fa[x]=getfa(fa[x]);}
void update(int x)
{
  size[x]=size[c[x][0]]+size[c[x][1]]+1;
}
void rot(int x,int kind)
{
  int y=pre[x],z=pre[y];
  pre[c[x][kind]]=y;c[y][!kind]=c[x][kind];
  pre[y]=x;c[x][kind]=y;
  pre[x]=z;if (z) c[z][c[z][1]==y]=x;
  update(y);update(x);
}
void splay(int x)
{
  int kind,y,z;
  while (pre[x])
	{
	  y=pre[x];
	  if (!pre[y]) rot(x,c[y][0]==x);
	  else
		{
		  z=pre[y];kind=c[z][0]==y;
		  if (c[y][kind]==x) rot(x,!kind);else rot(y,kind);
		  rot(x,kind);
		}
	}
}
void ins(int &x,int t)
{
  while(c[x][v[x]<v[t]]) x=c[x][v[x]<v[t]];
  c[x][v[x]<v[t]]=t;pre[t]=x;
  c[t][0]=c[t][1]=0;size[t]=1;
  x=t;splay(x);
}
void push(int x)
{
  if (!x) return;
  push(c[x][0]);
  st[++top]=x;
  push(c[x][1]);
}
void merge(int x,int y)
{
  splay(x);splay(y);
  if (size[x]<size[y]) swap(x,y);
  int k=fa[x];top=0;
  push(y);
  while (top) ins(x,st[top--]);
  fa[fa[y]]=k;
}
int findkth(int x,int k)
{
  if (k>size[x]) return -1;
  if (k<=size[c[x][0]]) return findkth(c[x][0],k);
  if (k==size[c[x][0]]+1) return x;
  return findkth(c[x][1],k-size[c[x][0]]-1);
}
int main()
{
  freopen("2733.in","r",stdin);
  scanf("%d%d",&n,&m);
  for (i=1;i<=n;i++) scanf("%lld",&v[i]),fa[i]=i,pre[i]=0,size[i]=1,c[x][1]=c[x][0]=0;
  for (i=1;i<=m;i++)
	{
	  scanf("%d%d",&x,&y);
	  if (getfa(x)!=getfa(y)) merge(x,y);
	}
  scanf("%d",&q);
  while (q--)
	{
	  scanf("%s%d%d",s,&x,&y);
	  if (s[0]=='B') 
		{
		  if (getfa(x)!=getfa(y)) merge(x,y);
		}
	  else splay(x),printf("%d\n",findkth(x,y));
	}
}