[SDOI2013]森林[树上主席树]

3 篇文章 0 订阅
1 篇文章 0 订阅

传送门ovo

我的主席树启蒙题目之一(之前是普通模板)

对于一棵树,我们如何使用主席树算法?

答案是像树上差分那样,以父亲为基础向儿子建立主席树,这样就可以利用树的优秀的dfs性质。

每个主席树维护的是自己到根的所有信息。

那么查询两个点x,y的信息就是经典的x+y-lca(x,y)-fa(lca(x,y) )。(可以脑补一下)

然后对于连边操作,我们使用启发式合并,将size比较小的树合并向比较大的树,对里面所有点的lca倍增数组重新赋值。

代码如下,应该可能有讲解

#include<bits/stdc++.h>
using namespace std;
#define in read()
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}
	return cnt*f;
}
const int N=80003;
int t[N],dep[N],fa[N][17],son[N],first[N];//主席树根,深度,倍增数组,子树大小,邻接表 
int nxt[N*4],to[N*4],tot;int cnt;//邻接表,cnt是主席树的id用 
int L[N*600],R[N*600],sum[N*600];//主席树 由于本题建立的主席树太多,请把内存开大 
int n,m,q,M,a[N],b[N];int F[N];int vis[80003];//m为离散化后的size,M为初始边数,ab离散化,F并查集用,vis用于在森林中判断是否查询 
void add(int a,int b){
	nxt[++tot]=first[a];first[a]=tot;to[tot]=b;
}
int build(int l,int r){
	int root=++cnt;
	int mid=(l+r)>>1;
	if(l<r){
		L[root]=build(l,mid);R[root]=build(mid+1,r);
	}
	return root;
}
int update(int pre,int l,int r,int key){
	int root=++cnt;int mid=(l+r)>>1;
	R[root]=R[pre],L[root]=L[pre],sum[root]=sum[pre]+1;
	if(l<r){
		if(key<=mid){
			L[root]=update(L[pre],l,mid,key);
		}else R[root]=update(R[pre],mid+1,r,key);
	}
	return root;
}
void dfs(int u,int faa,int x){//dfs修改倍增数组,子树大小son,并查集F,建立主席树。 
	fa[u][0]=faa;
	for(int i=1;i<=16;i++){
		fa[u][i]=fa[fa[u][i-1]][i-1];
	}son[x]++;dep[u]=dep[faa]+1;vis[u]=1;
	int key=lower_bound(b+1,b+n+1,a[u])-b;
	F[u]=faa;
	t[u]=update(t[faa],1,m,key);
	for(int i=first[u];i;i=nxt[i]){
		int v=to[i];if(v==faa)continue;
		dfs(v,u,x);
	}
}
int find(int u){
	if(u==F[u])return u;
	return F[u]=find(F[u]);
}
int findlca(int a,int b){
	if(a==b)return a;
	if(dep[a]<dep[b])swap(a,b);
	for(int i=16;i>=0;i--){
		if(dep[fa[a][i]]>=dep[b]){
			a=fa[a][i];
		}
	}
	if(a==b)return a;
	for(int i=16;i>=0;i--){
		if(fa[a][i]!=fa[b][i]){
			a=fa[a][i],b=fa[b][i];
		}
	}
	return fa[a][0];
}
int query(int a,int bb,int lca,int lcaa,int l,int r,int key){//两个点,lca,fa(lca),左,右,查询第key大 
	if(l==r)return b[l];
	int mid=(l+r)>>1;
	int sz=sum[L[a]]+sum[L[bb]]-sum[L[lca]]-sum[L[lcaa]];//x+y-lca-fa(lca)
	if(key<=sz){
		return query(L[a],L[bb],L[lca],L[lcaa],l,mid,key);
	}
	else return query(R[a],R[bb],R[lca],R[lcaa],mid+1,r,key-sz);
}
int main(){
	n=in;n=in;M=in;q=in;
	for(int i=1;i<=n;i++){
		a[i]=b[i]=in;
	}sort(b+1,b+n+1);m=unique(b+1,b+n+1)-b-1;
	for(int i=1;i<=M;i++){
		int x=in;int y=in; add(x,y);add(y,x);
	}
	t[0]=build(1,m);
	for(int i=1;i<=n;i++){
		if(!vis[i]){//森林判断 
			dfs(i,0,i);
		}
		F[i]=i;//甩进来一起搞定 
	}int lastans=0;
	char ch[3];int x,y,z;
	while(q--){
		scanf("%s",ch);
		x=in;y=in;x=x^lastans,y=y^lastans;
		if(ch[0]=='Q'){
			z=in;z=z^lastans;
			int lca=findlca(x,y);
			lastans=query(t[x],t[y],t[lca],t[fa[lca][0]],1,m,z);
			printf("%d\n",lastans);
		}
		else{
			add(x,y);add(y,x);
			int u=find(x),v=find(y);
			if(son[x]<son[y]){//保证x,u对应比较大的树 
				swap(u,v);swap(x,y);
			}
			dfs(y,x,u);//对小的树重新修改 
		}
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值