概述
树链剖分能将一棵树剖分成若干条链的形式。从而能在树上用一些线性数据结构如线段树,树状数组等维护。树链剖分分为重链剖分,长链剖分,LCT 剖分等。最常用的是重链剖分。本文将讲解重链剖分。
本文将持续更新。
我们接下来给出一些定义。
-
重儿子:指一个节点所有儿子中,子树最大的儿子。
-
轻儿子:指一个节点除了重儿子其他的儿子。
-
重边:连接一个节点和它重儿子的边。
-
轻边:连接一个节点和它轻儿子的边。
(图源 https://blog.csdn.net/qq_41418281/article/details/108220247 ,侵删。)
在上图中,红色的节点即为重儿子,红色的边为重链。黄色的点和黄色的边分别对应其轻儿子和轻链。
实现
实现重链剖分,我们需要两边 dfs。第一遍 dfs 预处理出重儿子,每个节点的父节点(便于后面操作),子树大小(重儿子),节点深度(便于后面操作)。
第二遍 dfs,我们找到每条重链的链头(top)
这里给出参考代码。
重链剖分
void dfs1(int p, int d)
{int size = 1, ma = 0;depth[p] = d;for (auto q : Edge[p])if (!depth[q]){dfs1(q, d + 1);f[q] = p;size += siz[q];if (siz[q] > ma)son[p] = q, ma = siz[q];}siz[p] = size;
}
void dfs2(int p)
{
// cout<<u<<"qwq"<<endl;;for(auto q:Edge[p]){if(!top[q]) {if(q == son[p]) top[q] = top[p];else top[q] = q;dfs2(q);}}
}
性质
通过上图,不难发现,重链是一条以轻儿子为链头的一条链(根节点除外),而轻链是连接重链与重链之间的桥梁。每个重链的链头的父亲一定在另外一条重链上。因此,我们记录每个节点的父亲,每条链的链头,就能快速实现重链与重链之间的跳跃。
其次,每个节点最多只能位于一条重链上。且一棵节点数量为 \(n\) 的树,从树上任意一条节点走到根节点,所经过的轻边数量不超过 \(\log n\) 条。
剖分完成后,我们便能在树上实现快速跳跃,重链剖分求解 LCA 是一种典型应用。下文将讲解。
重链剖分求 LCA
下文假设要求 \(a,b\) 的 LCA。
首先,若 \(a,b\) 位于同一条链上,则 LCA 即为深度较小的那个点。
若不在同一条链上。则 LCA 要么在链头深度较小的那条链上,要么就是两个链头的 LCA。绝不可能在链头深度较大的链上。这很显然,一条链只有到了链头才有轻边与其他重链连接。因此,我们不断让链头深度较大的往上跳到上一条链,直到到达同一条链为止。
具体实现见代码。
重链剖分求 LCA(省去重链剖分部分)
int lca(int a,int b)
{if(a > b) swap(a,b);while(top[a] != top[b]){// cout<<a<<" "<<b<<endl;if(depth[top[a]] > depth[top[b]]) a = f[top[a]];else b = f[top[b]];}return (depth[a] > depth[b] ? b : a);
}