长链剖分学习笔记

长链剖分学习笔记

简介

长链剖分也是一种树链剖分,平时咱们说树链剖分,通常都是直接默认为轻重链剖分。
轻重链剖分的优秀性质在于从任意一个点开始,向上跳跃,跳过的重链数量不会超过\(log\)级别。
这样子能够很优秀的解决两点之间链的问题。
对于解决一些子树的信息问题,咱们能够用\(dsu\ on\ tree\)的思路,保证了每一个点向上修改的次数不超过\(log\)次,也能够很方便的解决一些问题。而长链剖分则是经过修改剖分链的方式,经过维护一些信息,能够在更有优秀的时间中解决一部分问题。
长链剖分十分相似于轻重链剖分,可是咱们稍加修改,将每次选择子树大小最大的儿子做为重儿子变成了选择子树深度最大的那个儿子做为重儿子。而后将全部点和它的重儿子之间的边认为是重边,若是咱们把他们在树中所有加粗,那么原树就被分割成了若干条链。由于不少东西都和轻重链剖分是相同的,因此这一部分我写的很简单。甚至于连二者之间的代码都是很是的类似的。html

void dfs1(int u,int ff)
{
    md[u]=dep[u]=dep[ff]+1;fa[u]=ff;
    for(int i=h[u];i;i=e[i].next)
    {
        int v=e[i].v;if(v==ff)continue;
        dfs1(v,u);
        if(md[v]>md[hson[u]])hson[u]=v,md[u]=md[v];
    }
}
void dfs2(int u,int tp)
{
    top[u]=tp;len[u]=md[u]-dep[top[u]]+1;
    if(hson[u])dfs2(hson[u],tp);
    for(int i=h[u];i;i=e[i].next)
        if(e[i].v!=fa[u]&&e[i].v!=hson[u])
            dfs2(e[i].v,e[i].v);
}

稍微介绍一下每一个数组表示的含义分别是什么。
\(dep\)是深度,\(fa\)是父亲节点,\(md\)\(maxdep\)也就是子树中的最大深度
\(hson\)是重儿子,\(top\)是这条重链深度最小的点,也就是重链的顶点,\(len\)是重链的长度。
代码应该仍是比较清楚地,因此就不在过多的解释了。
若是没有学太轻重链剖分,能够先去学习一下,大概直接百度树链剖分就行了。算法

简单的性质

性质很少,长链剖分的重点在于题目。可是全部题目的复杂度都和性质相关。数组

性质一

全部链长度的和是\(O(n)\)级别的。学习

证实:优化

全部点在且仅在一条重链之中,永远只会被计算一次,由于链长的总和是\(O(n)\)级别的。spa

性质二

任意一个点的\(k\)次祖先\(y\)所在的长链的长度大于等于\(k\)指针

证实:
假如\(y\)所在的长链的长度小于\(k\),那么它所在的链必定不是重链,由于\(y-x\) 这条链显然更优,那么\(y\)所在的重链长度至少为\(k\),性质成立。
不然\(y\)因此在长链长度大于等于\(k\),性质成立。code

性质三

任何一个点向上跳跃重链的次数不会超过\(\sqrt n\)htm

证实:
若是一个点\(x\)从一条重链跳到了另一条重链上,那么跳跃到的这条重链的长度不会小于以前的重链长度。
那么在最坏的状况下,重链长度分别为\(1,2,3,...,\sqrt n\),也就是最多跳跃\(\sqrt n\)次。
从这点上就能够看出,若是用长链剖分来解决两点之间的链以及\(LCA\)问题,复杂度是不优于树链剖分的。blog

一些应用

1、\(O(nlogn)-O(1)\)计算\(k\)次祖先

\(k\)次祖先咱们有几种方法,能够树链剖分以后跳重链,这样是\(O(n)-O(logn)\)
还能够提早预处理好倍增数组,这样是\(O(nlogn)-O(logn)\)的。
我以前还本身\(yy\)了一种辣鸡作法,先树链剖分再倍增,彷佛能够作到\(O(nlogn)-O(loglogn)\)
还能够对于每一个点,暴力维护\([1,\sqrt n]\)次祖先,这样子是\(O(n\sqrt n)-O(\sqrt n)\)
固然,若是支持离线的话,能够作到\(O(n+q)\),只须要\(dfs\)的时候维护一个栈就行了。
然而这些都不够优秀,咱们来考虑一下长链剖分的性质,看可否优化上述东西。

咱们再看看\(O(n\sqrt n)-O(\sqrt n)\)的作法,它能够结合倍增,这样的话,它的本质就是优化掉了很小的几个二进制位。再回头看看上面的第二条性质:\(k\)次祖先所在的重链长度不小于\(k\)。利用这个性质,咱们就能够获得一个很秒的方法了。咱们把\(k\)折半,假设是\(r\),不难发现\(r\)次祖先所在的重链长度不短于\(r\)。若是咱们提早维护出每条重链从上往下的每个点,以及对于每一个重链的顶端,维护它的重链长度个祖先,这样子对于每次找到\(r\)次祖先以后,咱们就能够经过一些判断,找到\(k\)次祖先的位置,而最后这一次寻找不难发现能够利用上面预处理出来的重链和祖先\(O(1)\)的计算。那么如今的复杂度瓶颈又回到了找\(r\)次祖先。咱们直接找\(r\)次祖先真的优秀吗?若是利用倍增寻找的话,仍然是\(O(logn)\)的复杂度。可是咱们发现\(r\)只须要知足\(r>k/2\)就能够向上面那么作了,所以咱们令\(r\)\(k\)的最高二进制位,也就是\(r=highbit(k)\),这样子就能够倍增预处理出来\(r\)倍祖先,而后\(O(1)\)找到\(k\)次祖先了。

补充一些复杂度以及空间的细节:根据性质一,对于每一个重链的顶点,维护整条链以及它小于链长次祖先,由于总的点数是\(O(n)\)级别的,因此这里的时空负载度都是\(O(n)\)\(highbit\)显然能够\(O(n)\)预处理,这样子时空复杂度仍是\(O(n)\)。倍增的时空复杂度是\(O(nlogn)\),这个方法的复杂度瓶颈在此。

综上所述,咱们获得了一个预处理\(O(nlogn)\),单次询问\(O(1)\)的方法。

代码&题目连接

2、快速计算可合并的以深度为下标的子树信息

这个思路十分相似于\(dsu\ on\ tree\),咱们长链剖分以后,每次不从新计算,所有继承重儿子的值,而后再把其余的全部轻儿子的贡献额外的算进来。

直接这样子说十分的不清晰,咱们找到题目来讲。

BZOJ4543 Hotel增强版

题目&代码连接

由于会在这里写比较详细的题解,因此上面写得很\(Simple\)
咱们先考虑一个\(O(n^2)\)\(dp\),也就是原题的作法。
咱们考虑一下,三个点两两的距离相同是什么状况,

1.存在一个三个点公共的\(LCA\),因此咱们在\(LCA\)统计答案便可。

2.存在一个点,使得这个点到另外两个子树中距离它为\(d\)的点以及这个点的\(d\)次祖先。

因此,设\(f[i][j]\)表示以\(i\)为根的子树中,距离当前点为\(j\)的点数。
\(g[i][j]\)表示以\(i\)为根的子树中,两个点到\(LCA\)的距离为\(d\),而且他们的\(LCA\)\(i\)的距离为\(d-j\)的点对数。

考虑合并的时候的转移:
\(ans+=g[i][0],ans+=g[i][j]*f[son][j-1],f[i][j]+=f[son][j-1],g[i][j]+=g[son][j+1]\)
转移的正确性比较显然,不在多讲了,并非这里的重点。
这样子的复杂度是\(O(n^2)\)的。

咱们观察一下转移的时候有这样两步:\(f[i][j]+=f[son][j-1],g[i][j]+=g[son][j+1]\)
若是咱们钦定一个儿子的话,那么这个数组是能够直接赋值的,并不须要再重复计算。
因此咱们用指针来写,也就是:\(f[i]=f[son]-1,g[i]=g[son]+1\)
若是整棵树是链咱们发现复杂度能够作到\(O(n)\),既然如此,咱们推广到树。
咱们进行长链剖分,每次钦定从重儿子直接转移,那么咱们还须要从轻儿子进行转移。
不难证实全部轻儿子都是一条重链的顶部,转移时的复杂度是重链长度。
那么,复杂度拆分红两个部分:直接从重儿子转移\(O(1)\),从轻儿子转移\(O(\sum len)\)
发现每一个点有且仅有一个父亲,所以一条重链算且仅被一个点暴力转移,而每次转移复杂度是链长。
因此全局复杂度是\(\sum\)链长,也就是\(O(n)\),所以总复杂度就是\(O(n)\)

这样子写下来,发现长链剖分以后,咱们的复杂度变为了线性。
可是注意到复杂度证实中的一点:转移和链长相关。
而链长和什么相关呢?深度。因此说对于这一类与深度相关的、能够快速合并的信息,使用长链剖分能够优化到一个很是完美的复杂度。若是须要维护的与深度无关的信息的话,或许\(dsu\ on\ tree\)是一个更好的选择。

这里再额外补充一道题目,彷佛都比较简单:(尽然找不到别的题目了)

【BZOJ3653】谈笑风生

【CF1009F】Dominant Indices

【COGS2652】秘术「天文密葬法」

题解啥的直接戳连接吧。

3、维护一些奇怪的贪心

BZOJ3252 攻略

题面&题解&代码

这题比较神仙,对于长链剖分的运用也是很妙的,思路能够借鉴一下。

这里再也不过细的讨论这部份内容。

Ending

根据题目数量的分布,也不难看出长链剖分咱们用的最多的仍然是维护和深度相关的信息。由于求\(k\)次祖先预处理的复杂度达到了\(O(nlogn)\),成为了复杂度瓶颈,只有当询问次数很是大的时候,才能体现出长链剖分\(O(1)\)回答的优越性。同理,维护一类贪心题,彷佛题目也比较固定,而且能够用别的方法解决。只有在维护和深度相关的能够快速合并的信息的时候,时间复杂度能够作到\(O(n)\)级别,而包括轻重链剖分在内的一些算法通常都只能作到\(O(nlogn)\)级别,利用长链剖分能够作到更大的数据范围。

咕咕咕,这篇文章就到这里了,主要是\(yyb\)太弱了,难题写不动,就只能写写这些比较简答的题目。

相关文章
相关标签/搜索