• 浅谈长链刨分


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

    存在几个性质: 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 ",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大的重链即可。

  • 相关阅读:
    djinn:1 Vulnhub Walkthrough
    面试题:HTTP协议工作原理
    面试题:URI和URL的区别
    面试题:http和https的区别?什么是http无状态协议?什么是本地存储?
    Vue+Element 踩坑记录
    面试题:Vue的生命周期
    面试题:组件封装
    面试题:vuex
    面试题:callback
    面试题---华为机试在线训练:字符串最后一个单词的长度
  • 原文地址:https://www.cnblogs.com/chdy/p/12448195.html
Copyright © 2020-2023  润新知