题意:
给定n个点和它们的排名,支持两种操作:
1.连接两个点
2.询问某个点所在的联通块内排名第k大的是哪个点。
<脑洞第三个操作:删除两个点的连边>
首先被剧透是线段树的合并。
我仔细想了想这东西应该是很难写的。
hzt:就两行你快去写!
QAQ
然后我仔细想了想大概这样:
对于每个联通块开一个线段树,某个线段树的某段区间[l,r]意味着排名在[l,r]之间的节点信息。
也就是说假设我们有点x所在的联通块。
对点x所在的联通块开线段树,然后1~n区间,线段树每个节点表示在点x所在的联通块内,排名在[l,r]的节点信息。在这里,节点信息就是排名[l,r]的有多少个点在这个线段树里。
然后我们只要在线段树上二分即可。
如果说要合并两个联通块的话,那么我们只需要在并查集上把这两个联通块连起来,然后把这两个联通块对应位置的信息全都加起来即可,但原来的被合并的根节点的信息由于并查集的缘故就没办法在查询了。
再理一下思路:
1.用并查集维护连通性。
2.开权值的线段树,一个权值线段树维护一个联通块,动态开点。
3.询问时在线段树上二分。
4.修改时,合并并查集,并且合并对应并查集所在的根的那两个线段树。
//5.AC
#include <cstdio>
#include <cstring>
#define u t[x]
#define o t[y]
#define Rep(i,n) for(int i = 1;i <= n;i ++)
#define mid (l + r >> 1)
#define ulfc t[u.lc]
#define urtc t[u.rc]
#define lson u.lc,l,mid
#define rson u.rc,mid + 1,r
using namespace std;
const int N = 100005;
int fa[N],rt[N],w[N],Rank[N],tot = 0,n,m,Q;
struct Seg{int lc,rc,cnt;}t[N * 32];
void rd(int &x){x = 0;char ch = getchar();while(ch > '9' || ch < '0')ch = getchar();while(ch >= '0' && ch <= '9')x = 10 * x + ch - '0',ch = getchar();}
int find(int x){return x == fa[x] ? x : fa[x] = find(fa[x]);}
void Merge(int &x,int y)
{
if(!x || !y){x += y;return;}
u.cnt += o.cnt;
Merge(u.lc,o.lc),Merge(u.rc,o.rc);
}
void Ins(int &x,int l,int r,int val)
{
if(!x)x = ++ tot;
if(l == r){u.cnt = 1;return;}
if(val <= mid)Ins(lson,val);
else Ins(rson,val);
u.cnt = ulfc.cnt + urtc.cnt;
}
int Qry(int x,int l,int r,int k)
{
if(l == r){return Rank[l];}
if(k <= ulfc.cnt)return Qry(lson,k);
else return Qry(rson,k - ulfc.cnt);
}
int main()
{
rd(n),rd(m);
Rep(i,n)rd(w[i]),fa[i] = i,Rank[w[i]] = i;
Rep(i,m)
{
int _x,_y;
rd(_x),rd(_y);
_x = find(_x),_y = find(_y);
fa[_x] = _y;
}
Rep(i,n)Ins(rt[find(i)],1,n,w[i]);
rd(Q);
Rep(i,Q)
{
char op[5];
scanf("%s",op);
if(op[0] == 'Q')
{
int pos,k;
rd(pos),rd(k);pos = find(pos);
if(t[rt[pos]].cnt < k)puts("-1");
else printf("%d\n",Qry(rt[pos],1,n,k));
}
else
{
int _x,_y;
rd(_x),rd(_y);
_x = find(_x),_y = find(_y);
if(_y == _x)continue;
fa[_y] = _x;
Merge(rt[_x],rt[_y]);
}
}
return 0;
}