LG-P3244-永无乡
题目大意
给你n个点,每个点有权值k,现有两种操作:将两个点所在联通块合并,查询某个点所在联通块权值第k小是哪个数
思路
一看就知道是线段树合并,但是写不来。
用并查集来维护联通块,合并的同时再合并线段树即可,注意是权值线段树,并且输出的是点的编号不是值。
$Code$
#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
const int N=1e5+5,M=3e5+5;
int val[N],fa[N],rt[N],lsh[N];
int ls[N*20],rs[N*20],sum[N*20],co;
int n,m,t;
int fi(int x){
if(fa[x]==x) return x;
return fa[x]=fi(fa[x]);
}
void mer(int x,int y){
fa[fi(x)]=fi(y);
}
//线段树
void pushup(int node){
sum[node]=sum[ls[node]]+sum[rs[node]];
}
int tr_mer(int x,int y,int l,int r){
if(!x) return y;
if(!y) return x;
if(l==r){
sum[x]+=sum[y];
return x;
}
ls[x]=tr_mer(ls[x],ls[y],l,mid);
rs[x]=tr_mer(rs[x],rs[y],mid+1,r);
pushup(x);
return x;
}
int change(int node,int l,int r,int pos,int x){
if(!node) node=++co;
if(l==r){
sum[node]+=x;
return node;
}
if(pos<=mid) ls[node]=change(ls[node],l,mid,pos,x);
else rs[node]=change(rs[node],mid+1,r,pos,x);
pushup(node);
return node;
}
int ask(int node,int l,int r,int k){
if(!node || sum[node]<k) return -1;
if(l==r) return lsh[l];
if(sum[ls[node]]>=k) return ask(ls[node],l,mid,k);
else return ask(rs[node],mid+1,r,k-sum[ls[node]]);
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%d",&val[i]),lsh[val[i]]=i,rt[i]=change(0,1,n,val[i],1),fa[i]=i;
for(int i=1;i<=m;++i){
int x,y;
scanf("%d%d",&x,&y);
x=fi(x),y=fi(y);
mer(x,y);
rt[fi(x)]=tr_mer(rt[x],rt[y],1,n);
}
scanf("%d",&t);
for(int i=1;i<=t;++i){
char op;int x,y;
cin>>op;scanf("%d%d",&x,&y);
if(op=='Q')
printf("%d\n",ask(rt[fi(x)],1,n,y));
else{
x=fi(x),y=fi(y);
mer(x,y);
rt[fi(x)]=tr_mer(rt[x],rt[y],1,n);
}
}
return 0;
}
\({\color{skyblue}{谨此纪念我过的第一道线段树合并的题}}\)