[BZOJ 3123] 森林
Link:
Solution:
主席树+启发式合并
以前好像做的主席树都是在序列上的……在树上的主席树这样处理:
每个节点的主席树维护其到根节点的路径上的值,以其父节点为模板构造
那么为了取出$(u,v)$路径上的值用$seg[x]+seg[y]-seg[lca]-seg[f[lca]]$就行了
这其实可以说是树上差分的基本套路,而主席树不就是利用了差分性质嘛
接下来就要处理合并了,有一种感性方式就是将小的连通块并入大的中
这样的方式就叫做启发式合并了($DSU on Tree$),可以证明其总复杂度为$O(n*log(n))$
证明:
对于每棵以$v$为根的子树而言,仅当$edge(v,f[v])$为轻边时才会将所有子树内节点更新
如果按每个点计算贡献的方式,根据树链剖分中的性质:一个点到根的路径上轻边/重链个数不超过$log(n)$
可以发现每个节点最多更新$log(n)$次,因此复杂度上界就是$n*log(n)$
实现时用并查集维护连通性,每次找到$x,y$的代表节点$f[x],f[y]$,假设$sz[x]<sz[y]$
合并时图中将$x,y$连边,并查集中将$f[x],f[y]$连边
并将以$x$为根的子树中的点以$y$节点为初始点进行暴力更新
根据上述证明每个点保证最多更新$log(n)$次,但更新主席树也要$log(n)$,因此总复杂度为$O(n*log(n)^2)$
Tip:这次空间又开小了……难道不是$O(n*log(n))$?以后是真要学学怎么算空间复杂度了
Code:
#include <bits/stdc++.h> using namespace std; const int MAXN=8e4+10,MAXM=20000005; struct PrTree{int ls,rs,cnt;}seg[MAXM];//一开始空间又开小了…… struct edge{int nxt,to;}e[MAXN<<2]; int f[MAXN][25],dep[MAXN],sz[MAXN],fa[MAXN]; int T,n,m,t,x,y,k,rt[MAXN],head[MAXN],dat[MAXN],dsp[MAXN],etot,tot,cnt,res; void add_edge(int from,int to) {e[++etot].nxt=head[from];e[etot].to=to;head[from]=etot;} int LCA(int x,int y) { if(dep[x]<dep[y]) swap(x,y); int t=dep[x]-dep[y]; for(int i=0;i<=20;i++) if(t&(1<<i)) x=f[x][i]; for(int i=20;~i;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return (x==y)?x:f[x][0]; } void Insert(int pre,int &cur,int pos,int l,int r) { cur=++cnt;seg[cur]=seg[pre];seg[cur].cnt++; if(l==r) return;int mid=(l+r)>>1; if(pos<=mid) Insert(seg[pre].ls,seg[cur].ls,pos,l,mid); else Insert(seg[pre].rs,seg[cur].rs,pos,mid+1,r); } int Query(int a,int b,int c,int d,int k,int l,int r) { if(l==r) return dsp[l]; int mid=(l+r)>>1,sum=seg[seg[a].ls].cnt+seg[seg[b].ls].cnt-seg[seg[c].ls].cnt-seg[seg[d].ls].cnt; if(k<=sum) return Query(seg[a].ls,seg[b].ls,seg[c].ls,seg[d].ls,k,l,mid); else return Query(seg[a].rs,seg[b].rs,seg[c].rs,seg[d].rs,k-sum,mid+1,r); } int Find(int x){return (fa[x]==x)?x:fa[x]=Find(fa[x]);} void dfs(int x,int anc) { dep[x]=dep[anc]+1;f[x][0]=anc; for(int i=1;i<=20;i++) f[x][i]=f[f[x][i-1]][i-1]; Insert(rt[anc],rt[x],dat[x],1,tot); for(int i=head[x];i;i=e[i].nxt) if(e[i].to!=anc) dfs(e[i].to,x); } void Link(int x,int y) { int posx=Find(x),posy=Find(y); if(sz[posx]>sz[posy]) swap(x,y),swap(posx,posy); sz[posy]+=sz[posx];fa[posx]=posy; add_edge(x,y);add_edge(y,x);dfs(x,y); } int main() { scanf("%d%d%d%d",&T,&n,&m,&t); for(int i=1;i<=n;i++) scanf("%d",&dat[i]),dsp[i]=dat[i]; sort(dsp+1,dsp+n+1);tot=unique(dsp+1,dsp+n+1)-dsp-1; for(int i=1;i<=n;i++) dat[i]=lower_bound(dsp+1,dsp+n+1,dat[i])-dsp; for(int i=1;i<=n;i++) sz[i]=1,fa[i]=i; for(int i=1;i<=m;i++) scanf("%d%d",&x,&y),Link(x,y); for(int i=1;i<=n;i++)//一定要对其它孤立点初始化 if(!dep[i]) dfs(i,0); for(int i=1;i<=t;i++) { char s[20];scanf("%s%d%d",s,&x,&y); x^=res;y^=res; if(s[0]=='Q') { scanf("%d",&k);k^=res; int lca=LCA(x,y),flca=f[lca][0]; printf("%d\n",res=Query(rt[x],rt[y],rt[lca],rt[flca],k,1,tot)); } else Link(x,y); } return 0; }