题解 P3302 【[SDOI2013]森林】

IC_QQQ

2019-06-05 11:17:12

Solution

### 树上的主席树(利用LCA)+启发式合并 有兴趣的可以去看看简化版(去掉连边操作)题目[P2633 Count on a tree](https://www.luogu.org/problemnew/show/P2633)。~~获得双倍经验~~。 这道题主要有两个操作: 1. 求路径 **(u,v)** 上第**k**小点。 2. 在点 **x,y** 间连一条无向边。 第一个操作很明显,在树上建立主席树,主席树 **i** 维护从根节点 **root** 到点 **i** 路径上所有点的信息。 查询路径 **(u,v)** 上第**k**小点也容易想到,需要用到 **LCA** 。 我们找到 **(u,v)** 的最近公共祖先 **lca** 和 **lca** 的父节点 **fa_lca** 。维护这四个点的信息进行查询就可以了。 对于第二个操作,为了使时间更优,我们需要用到启发式合并的思想。~~启发式合并听上去玄妙无比,其实很普通~~。简而言之就是记录每棵树的大小,连边时把小树连接到大树上去,重构小树中的主席树、**LCA**相关数组。 ~~其他的一些操作像离散化大家都会吧。~~ ### 真正的重点—关于这道题的特性: 在无数次 **RE** 第**2,3,6**个测试点后,我终于找到了坑点:问题出在 **LCA** 上。 下面是错误示范:(**ans[i][j]** 表示 **i** 的 **2^j** 级祖先) ```cpp void update_LCA(int u,int fa){ deep[u]=deep[fa]+1;ans[u][0]=fa; for(int i=1;i<=lg[deep[u]];i++)//这里的上界 ans[u][i]=ans[ans[u][i-1]][i-1]; for(int i=head[u];i;i=nxt[i]) if(to[i]!=fa) update_LCA(to[i],u); return; } ``` 只需要这里把循环的上界从 **lg[deep[u]]** 改为 **x** ,保证 **2^x>=n** 就可以了,比如 **18,19**等等。 为什么会这样,**lg[deep[u]]** 不是 **LCA** 的通用写法吗,**LCA** 的模板题也是写的**lg[deep[u]]**。 我翻讨论区,发现也有人困惑于此。~~然而,题解都巧妙地回避了这个问题,没有解答~~。 无奈,我下载了第**2**个测试点的数据,开始了漫长的探索之路。 终于,在数据中第**16058**次操作(询问操作),我发现了问题。 **lg[deep[u]]** 的确是 **LCA** 的通用写法,没有问题,这道题上行不通是这道题的 **特性** ,因为这道题有一个特殊的操作:**连边** 。 我们来看 **get_LCA** 操作: ```cpp int get_LCA(int u,int v){ if(deep[u]<deep[v]) swap(u,v); while(deep[u]>deep[v]) u=ans[u][lg[deep[u]-deep[v]]]; if(u==v) return u; for(int i=lg[deep[u]];i>=0;i--) if(ans[u][i]!=ans[v][i])//重点 u=ans[u][i],v=ans[v][i]; return ans[u][0]; } ``` 如果我们连边重构 **update_LCA** 时采用 **lg[deep[u]]** ,可能存在一种情况:这个点 **i** 原本的 **ans[i][j]**,**j** 最大已经大于了 **lg[deep[u]]** (即原来深度更深),但是我们更新只是更新到 **lg[deep[u]]** ,它上面的状态没有改变。在执行 **get_LCA** ,进行判断时: ```cpp if(ans[u][i]!=ans[v][i])//重点 u=ans[u][i],v=ans[v][i]; ``` 本来,这一次 **u,v** 不能被改变, 由于状态没有全部更新,**u,v** 被改变了,导致**lca**找错,进而答案错误,下一次进行异或操作时数字变得很大,数组越界直接导致 **RE** 。 举个栗子大家就知道了: ![](https://i.loli.net/2019/06/05/5cf73164c7b0042630.png) 假设 **ans[x]={root,6},ans[y]={root,7}**。 从 **u,v** 出发,模拟走一遍。 **i=2**时,**u**跳到了**x**。**v**跳到了**y**。 **i=1**时,**x**跳到了**6**,**v**跳到了**7**。 好了,已经出错了。 这就是这道题的特性,连边重构 **update_LCA** 时,上界很重要。 ~~至于为什么其他题上界可以是 **lg[deep[u]]** ,大家自己想想吧,不难。~~ ### 代码 ```cpp #include <bits/stdc++.h> using namespace std; const int N=8e4+5; int T,n,m,TT,last; int tot,row[N],s[N],size[N]; int cnt,to[4*N],nxt[4*N],head[4*N]; int from[N],lg[N],ans[N][35],deep[N]; struct Tree{ int ls,rs,si; }t[105*N]; int root[N],top; int in(){ int x=0,f=1;char ch=getchar(); while(ch>'9'||ch<'0'){ if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } void adds(int x,int y){ to[++cnt]=y;nxt[cnt]=head[x];head[x]=cnt; return; } void pre_work(){ int a,b;lg[0]=-1; T=in();n=in();m=in();TT=in(); for(int i=1;i<=n;i++) row[i]=s[i]=in(),lg[i]=lg[i>>1]+1,from[i]=i; sort(row+1,row+1+n); tot=unique(row+1,row+1+n)-(row+1); for(int i=1;i<=n;i++) s[i]=lower_bound(row+1,row+1+tot,s[i])-row; for(int i=1;i<=m;i++) a=in(),b=in(),adds(a,b),adds(b,a); return; } void built(int &pos,int pre,int l,int r,int wi){ t[pos=++top]=t[pre]; t[pos].si++; if(l==r) return; int mid=(l+r)>>1; if(wi<=mid) built(t[pos].ls,t[pre].ls,l,mid,wi); else built(t[pos].rs,t[pre].rs,mid+1,r,wi); return; } void dfs(int u,int fa,int rt){ built(root[u],root[fa],1,tot,s[u]); deep[u]=deep[fa]+1;ans[u][0]=fa; size[rt]++;from[u]=rt; for(int i=1;i<=18;i++) ans[u][i]=ans[ans[u][i-1]][i-1]; for(int i=head[u];i;i=nxt[i]) if(to[i]!=fa) dfs(to[i],u,rt); return; } int get_LCA(int u,int v){ if(deep[u]<deep[v]) swap(u,v); while(deep[u]>deep[v]) u=ans[u][lg[deep[u]-deep[v]]]; if(u==v) return u; for(int i=lg[deep[u]];i>=0;i--) if(ans[u][i]!=ans[v][i]) u=ans[u][i],v=ans[v][i]; return ans[u][0]; } int Answer(int u,int v,int og,int fg,int l,int r,int k){ if(l==r) return row[l]; int sum=t[t[u].ls].si+t[t[v].ls].si-t[t[og].ls].si-t[t[fg].ls].si; int mid=(l+r)>>1; if(k<=sum) return Answer(t[u].ls,t[v].ls,t[og].ls,t[fg].ls,l,mid,k); return Answer(t[u].rs,t[v].rs,t[og].rs,t[fg].rs,mid+1,r,k-sum); } int main(){ pre_work(); for(int i=1;i<=n;i++) if(from[i]==i) dfs(i,0,i); char ch[5];int x,y,k; for(int i=1;i<=TT;i++){ scanf("%s",ch);x=in()^last;y=in()^last; if(ch[0]=='Q'){ k=in()^last; int og=get_LCA(x,y),fg=ans[og][0]; last=Answer(root[x],root[y],root[og],root[fg],1,tot,k); printf("%d\n",last); } else{ adds(x,y);adds(y,x); int fx=from[x],fy=from[y]; if(size[fy]<size[fx]) dfs(y,x,fx); else dfs(x,y,fy); } } return 0; } ```