P3302 [SDOI2013]森林
看到森林有合并首先会想到 $LCT$ ,然后发现链上第 $K$ 小不可维护
感觉 $LCT$ 只维护合并也有点大材小用了,考虑合并时直接启发式合并就可以不用 $LCT$
然后求第 $K$ 小显然考虑主席树
对每个节点维护一个主席树,维护它到树根这的一段区间,那么当前节点的线段树可以直接借用父节点的线段树
并且因为主席树是可加减的,设节点 $x$ 的线段树为 $T[x]$,那么询问是就是在 $T[x]+T[y]-T[lca(x,y)]-T[fa[lca(x,y)]]$ 上面跑
每次启发式合并时把小的子树重构一波
因为值的范围很大所以要离散一下
更新一个节点的复杂度为 $O(log_n)$ ,启发式合并总复杂度为 $O(nlog_n)$
那么总复杂度 $O(nlog^2_n)$
最后注意一个可能只有我才要注意的细节:
如果预处理倍增数组是这样:
f[x][0]=fa[x]; for(int i=1;(1<<i-1)<=dep[x];i++) f[x][i]=f[f[x][i-1]][i-1];
并且查询时是这样:
for(int i=20;i>=0;i--) if(dep[f[x][i]]>=dep[y]) x=f[x][i]; if(x==y) return x; for(int i=20;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
那么就会 $GG$
本来这个板子在静态的树上是没问题的
但是我们合并子树时可能一个节点的 $dep$ 会变小,那么就无法完全覆盖上一次的数据
而查询时又是从最后面开始查,然后就会访问到以前的数据,直接导致 $GG$
查了将近两个小时......
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> #include<queue> using namespace std; typedef long long ll; inline int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } const int N=4e6+7,M=5e7+7; int fir[N],from[N<<1],to[N<<1],cntt;//存森林 inline void add(int a,int b) { from[++cntt]=fir[a]; fir[a]=cntt; to[cntt]=b; from[++cntt]=fir[b]; fir[b]=cntt; to[cntt]=a; } int n,m,Q,tot; int a[N],b[N];//a是原来的,b是离散化后的 inline int find(int x) { return lower_bound(b+1,b+tot+1,x)-b; }//在离散化后的数据中找到相应的位置 //以下主席树 int rt[N],L[M<<1],R[M<<1],T[M<<1],cnt; int pos,K,res; void insert(int &o,int l,int r,int pre) { if(!o) o=++cnt; T[o]=T[pre]+1; if(l==r) return; int mid=l+r>>1; if(pos<=mid) insert(L[o],l,mid,L[pre]),R[o]=R[pre];//另一边直接覆盖 else insert(R[o],mid+1,r,R[pre]),L[o]=L[pre]; } void query(int x,int y,int lca,int flca,int l,int r) { if(l==r) { res=b[l]; return; } int mid=l+r>>1,t=T[L[x]]+T[L[y]]-T[L[lca]]-T[L[flca]];// if(t<K) { K-=t; query(R[x],R[y],R[lca],R[flca],mid+1,r); } else query(L[x],L[y],L[lca],L[flca],l,mid); } //以下维护森林中的数据 int RT[N],RTsz[N],fa[N],dep[N],f[N][27]; //RT维护每个节点的根,RTsz维护一个根节点的子树大小,fa为父节点,dep为深度,f是倍增数组 bool vis[N];//vis用来判断一个点是否访问过,只为第一次预处理 void dfs(int x,int rrt)//dfs更新主席树和倍增数组 { pos=find(a[x]); insert(rt[x],1,tot,rt[fa[x]]);//直接修改 vis[x]=1; RT[x]=rrt; RTsz[rrt]++; dep[x]=dep[fa[x]]+1; f[x][0]=fa[x]; for(int i=1;i<=20;i++) f[x][i]=f[f[x][i-1]][i-1];//注意要全部覆盖! for(int i=fir[x];i;i=from[i]) { int &v=to[i]; if(v==fa[x]) continue; fa[v]=x; dfs(v,rrt); } } inline int LCA(int x,int y)//求LCA { if(dep[x]<dep[y]) swap(x,y); for(int i=20;i>=0;i--) if(dep[f[x][i]]>=dep[y]) x=f[x][i]; if(x==y) return x; for(int i=20;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return f[x][0]; } int tmp[N]; int main() { int x=read(),y,z; n=read(),m=read(),Q=read(); for(int i=1;i<=n;i++) tmp[i]=a[i]=read(); sort(tmp+1,tmp+n+1); for(int i=1;i<=n;i++) if(i==1||tmp[i]!=tmp[i-1]) b[++tot]=tmp[i];//离散化 for(int i=1;i<=m;i++) x=read(),y=read(),add(x,y);//连边 for(int i=1;i<=n;i++) if(!vis[i]) dfs(i,i);//预处理 int pre=0; char s[7]; while(Q--) { scanf("%s",s); x=read()^pre,y=read()^pre; if(s[0]=='Q') { z=read()^pre; int lca=LCA(x,y); res=0; K=z; query(rt[x],rt[y],rt[lca],rt[fa[lca]],1,tot); printf("%d\n",res); pre=res; continue; } if(RTsz[RT[x]]<RTsz[RT[y]]) swap(x,y);//启发式合并 add(x,y); fa[y]=x; dfs(y,RT[x]); } return 0; }