【权值线段树+启发式合并】森林

传送门

Q操作:第k小用主席树维护。

L操作:启发式合并暴力做就行了。因为这里保证了连边以后仍然是森林。

注意fa并查集数组和f倍增数组是不一样的,不要用混了。

 

这里的主席树维护的是当前节点到根节点的信息。

就是说:root[u]对应的这颗树就是从u结点到fa[u]结点(fa[u]是并查集的根,也就是u所在树的根)的这条链的信息。

开始的时候先把空树建出来,也就是把结点都建出来。然后找没被dfs过的点,把所有树给搞出来。

 

连边的时候,把被合并结点(子树size小一些)的这个树重建。

具体做法:从父亲节点那里继承过来,再增加一个儿子结点的信息就行了。

dfs暴力重建的时候有几个信息要更新:倍增数组、被合并子树结点的fa值(都指向size大一些的那个子树的集合根,这两个集合被合成了一个集合)、合并出来的集合的大小、结点深度。初始建森林的时候要记一个vis数组以区分不同的树。

 

蓝树大一些,红树小一些。那么把红树合到A上。从A开始dfs。大概就是这个意思。

 

然而这题有点坑啊,很容易RE。。而且testcase是打酱油的。。。。。

#include<bits/stdc++.h>
#define mid ((l+r)>>1)
using namespace std;
const int maxn=80010;
char op[5];
int n,m,t,testcase,M,x,y,g,k,lastans=0;
int Head[maxn],Next[maxn<<1],V[maxn<<1],val[maxn],cpy[maxn],fa[maxn],cnt=0;
int f[maxn][17],siz[maxn],dep[maxn],vis[maxn],root[maxn],tot=0;
struct node{int siz,ls,rs;}tr[maxn*600];
int read(){
	int x=0;char ch=getchar();
	while(!isdigit(ch)) ch=getchar();
	while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return x;
}
void add(int u,int v){Next[++cnt]=Head[u],V[cnt]=v,Head[u]=cnt;}
int getfa(int x){return (fa[x]==x)?(x):(fa[x]=getfa(fa[fa[fa[x]]]));}
void disc(){
	sort(cpy+1,cpy+n+1),M=unique(cpy+1,cpy+n+1)-cpy-1;
	for(int i=1;i<=n;++i) val[i]=lower_bound(cpy+1,cpy+M+1,val[i])-cpy;
}
int getlca(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);
	for(int i=16;i>=0;i--) if(dep[f[u][i]]>=dep[v]) u=f[u][i];
	if(u==v) return u;
	for(int i=16;i>=0;i--) if(f[u][i]!=f[v][i]) u=f[u][i],v=f[v][i];
	return f[u][0];
}
void build(int &root,int l,int r){
	tr[root=++tot].siz=0;
	if(l==r) return;
	build(tr[root].ls,l,mid);
	build(tr[root].rs,mid+1,r);
}
void Insert(int &rt,int pre,int l,int r,int val){
	tr[rt=++tot]=tr[pre],tr[rt].siz++;
	if(l==r) return;
	if(val<=mid) Insert(tr[rt].ls,tr[pre].ls,l,mid,val);
	else Insert(tr[rt].rs,tr[pre].rs,mid+1,r,val);
}
int query(int x,int y,int g,int fg,int l,int r,int k){
	if(l==r) return cpy[l];
	int now=tr[tr[x].ls].siz+tr[tr[y].ls].siz-tr[tr[g].ls].siz-tr[tr[fg].ls].siz;
	if(k<=now) return query(tr[x].ls,tr[y].ls,tr[g].ls,tr[fg].ls,l,mid,k);
	else return query(tr[x].rs,tr[y].rs,tr[g].rs,tr[fg].rs,mid+1,r,k-now);
}
void dfs(int u,int F,int rt){
	f[u][0]=F;for(int i=1;i<=16;++i) f[u][i]=f[f[u][i-1]][i-1];
	siz[rt]++,dep[u]=dep[F]+1,fa[u]=rt,vis[u]=1;
	Insert(root[u],root[F],1,M,val[u]);
	for(int i=Head[u];i;i=Next[i]) if(V[i]!=F)
		dfs(V[i],u,rt);
}
inline void print(int x){
	if(x>9) print(x/10);
	putchar(x%10+'0');
}
int main(){
	testcase=read(),n=read(),m=read(),t=read();
	for(int i=1;i<=n;++i) val[i]=read(),fa[i]=i,cpy[i]=val[i];
	while(m--) x=read(),y=read(),add(x,y),add(y,x);
	disc(),build(root[0],1,M);
	for(int i=1;i<=n;++i) if(!vis[i]) dfs(i,0,i);
	while(t--){
		scanf("%s",op);
		if(op[0]=='Q'){
			x=read()^lastans,y=read()^lastans,k=read()^lastans,g=getlca(x,y);
			lastans=query(root[x],root[y],root[g],root[f[g][0]],1,M,k);
			print(lastans),putchar('\n');
		}
		if(op[0]=='L'){
			x=read()^lastans,y=read()^lastans;
			add(x,y),add(y,x);
			int fx=getfa(x),fy=getfa(y);
			if(siz[fx]<siz[fy]) swap(x,y),swap(fx,fy);
			dfs(y,x,fx);
		}
	}
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值