浅谈长链刨分

对于树上问题我们一般还可以采取长链刨分进行树上问题的优化。
长链刨分就是以深度划分重儿子和轻儿子。

存在几个性质: 1. 所有链长度的和为O(n)级别的。 2. 任意一个点的k次祖先y所在的长链的长度大于等于k 3. 任何一个点向上跳跃重链的次数不超过$\sqrt{n}$ 证明3:一个点从一个重链上调到另一个重链上 重链的长度分部的最坏情况是1 2 3 4...$sqrt{n}$所以最多跳$\sqrt{n}$次。

应用:

  1. O(nlogn)-O(1)求K级祖先。

对于询问远大于点数求K级祖先时我们使用倍增可能会超时 但是我们进行倍增的预处理则不会 套上长链刨分我们即可O(1)回答。

具体实现:长链刨分过后我们对于每条长链维护该链长的祖先和儿子。我们利用倍增数组跳K的二进制位的最高位那一项 然后在跳到的点的长链顶点处求出。

所以 预处理nlogn 回答O(1).这样做的时间复杂度 每个点处预处理是链长之和所以为O(n),倍增预处理nlogn 考虑空间复杂度 显然也是O(n). ``` const int MAXN=500010; int n,q,rt,len,last; ui s;char ch; ll ans; int f[MAXN][21],Log[MAXN],son[MAXN],mx[MAXN],d[MAXN],top[MAXN]; int lin[MAXN],ver[MAXN],nex[MAXN]; //mx[x]表示以x点向下的最大深度 son[x]表示重儿子. //top[x]表示以x这条链上的祖先. vectoru[MAXN],v[MAXN]; inline void add(int x,int y) { ver[++len]=y; nex[len]=lin[x]; lin[x]=len; } inline ui get(ui x) { x^=x<<13;x^=x>>17; x^=x<<5;return s=x; } inline void dfs(int x) { d[x]=d[f[x][0]]+1;mx[x]=d[x]; for(int i=lin[x];i;i=nex[i]) { int tn=ver[i]; for(int j=1;j<=Log[d[x]];++j)f[tn][j]=f[f[tn][j-1]][j-1]; dfs(tn); if(mx[tn]>mx[x]) { son[x]=tn; mx[x]=mx[tn]; } } } inline void dfs(int x,int father) { top[x]=father; if(x==father) { for(int i=0,j=x;i<=mx[x]-d[x];++i)u[x].pb(j),j=f[j][0]; for(int i=0,j=x;i<=mx[x]-d[x];++i)v[x].pb(j),j=son[j]; } if(son[x])dfs(son[x],father); for(int i=lin[x];i;i=nex[i]) { int tn=ver[i]; if(tn!=son[x])dfs(tn,tn); } } inline int ask(int x,int k)//求x的k级祖先 { if(!k)return x; x=f[x][Log[k]];k-=1<=0?u[x][k]:v[x][-k]; } int main() { //freopen("1.in","r",stdin); n=read();q=read();ch=getc(); while(ch>='0'&&ch<='9') { s=s*10+ch-'0'; ch=getc(); } for(int i=1;i<=n;++i) { int x=read(); if(i!=1)Log[i]=Log[i>>1]+1; if(!x)rt=i; else add(x,i),f[i][0]=x; } dfs(rt);dfs(rt,rt); for(int i=1;i<=q;++i) { int x=(get(s)^last)%n+1; int k=(get(s)^last)%d[x]; last=ask(x,k); ans=ans^(1ll*i*last); } printf("%lld\n",ans); return 0; } ``` 2. 快速计算可合并的以深度为下标的子树信息。

类似于dsu on tree 长链刨分过后每次不重新计算 全部继承重儿子的值对于轻儿子再暴力进行合并。

例题:[luogu3565](https://www.luogu.com.cn/problem/P3565) 给定一颗树在树上选择三个点要求两两距离相等 求方案数。

不难想到我们先求出两个点的距离相等的方案数再找第三个点即可。 f[i][j]表示以i为根的子树内距i距离为j的点的个数。

我们在每个LCA处合并答案,先求出点对数再求答案最后别忘了还有父亲方面的累加。 但是这样我们需要先求出f数组 然后再进行类似容斥的dp..(自己yy的一个想法。

考虑不这样做我们直接以某个点为中心统计答案n^2 但是这和dp没什么关系了。

考虑这样做我么统计答案的时候并不在三个点共有的中心统计 而是在最上端的那个点统计。 具体考虑我们g[i][j]表示多少点对的LCA到i距离为d[x]-j 之所以是这个状态 是因为也只能是这个状态了还是可以推出来的。

不过转移有点繁琐 学到了一个新的技巧观察转移方程式...不好描述...

具体转移如下: ``` inline void dfs(int x,int father) { d[x]=d[father]+1;f[x][0]=1; son[x]=x; for(int i=lin[x];i;i=nex[i]) { int tn=ver[i]; if(tn==father)continue; dfs(tn,x); if(d[son[tn]]>d[son[x]])son[x]=son[tn]; for(int j=d[son[tn]];j>=0;--j) { if(j>=1)ans+=g[x][j]*f[tn][j-1]; ans+=f[x][j]*g[tn][j+1]; g[x][j]+=g[tn][j+1]; g[x][j+1]+=f[tn][j]*f[x][j+1]; f[x][j+1]+=f[tn][j]; } } } ```

当然这个dp还能优化。。

当然 我只是理解长链刨分的优化但是我不会指针分配的写法。

所以就咕咕咕了。

放两道例题:[codeforces1009F](https://www.luogu.com.cn/problem/CF1009F)

这道题可以写dsu 也可以线段树 当然还可以长链刨分优化dp了。最后咕咕咕。

[cogs2652]秘术「天文密葬法」 这个分数规划+长链刨分的题目 题目地址找了但是打不开。。

LINK:[攻略bzoj3252](http://www.lydsy.com/JudgeOnline/problem.php?id=3252)

题意:树版的k方格取数 贪心挺显然的,我们每次选取一条权值最大的路径将其清0.

为什么这样做是对的 其实可以考虑模拟费用流的思想我们这其实就是在模拟费用流。

这样做是$n^2$的 如何优化?考虑长链刨分。我们把权值改为链长 那么每次我们选取最长的重链.

其他链不受我们的影响 所以取前k大的重链即可。

posted @ 2020-03-09 13:14  chdy  阅读(191)  评论(0编辑  收藏  举报