长链剖分
WC听课时听到可以
$\Theta(1)$ 查询树上祖先的算法,叫长链剖分。然而讲课人非常厉害,说网上这种资料很多就没讲了。然而我姿势不够并搜不到。打听到该算法后在这里记录一下。
查询树上祖先的一般算法
静态树
静态的树上之前我只知道
构建出倍增表后,如果我们想往上方走
1 2 3 4 5 6 7 8 9 10 | function find_ancestor(u, d): k = 0 while d: if d & 1: u = f[u][k] d >>= 1 k++ return u |
动态树
当然需要LCT啦。首先access
到
长链剖分
十分出名的树链剖分是按照儿子节点的大小来挑选重儿子的,这种剖分方法保证了每个节点到根的路径上的轻边数量为
但是我们考虑另外一个事情:长链剖分之后,对于每个节点
那这有什么用?考虑另外一个事情:对于一个距离
因此实现的时候,需要计算倍增表和长链,这一步可以在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /** * @param u (int) 当前节点 * @param d (int) 距离 * @return (int) 返回从当前节点往上走d步的祖先 */ int find_ancestor(int u, int d) { assert(0 <= d && d <= depth[u]); // 必须保证祖先存在 if (d == 0) // d = 0特判 return u; int h = highbit[d]; // highbit用于记录数字1..n的最高位,提前预处理 u = f[h][u]; // f是倍增表,上跳2^k步 d -= 1 << h; assert(d < (1 << h)); // 满足事实1 int t = top[u]; // top记录每个节点所处的链的链顶 int pos = len[t] - 1 - (depth[u] - depth[t]) + d; // len记录链长 // chain是存储了2 * len[t]个节点的vector assert(0 <= pos && pos < chain[t].size()); // 满足事实2 return chain[t][pos]; // 返回答案 } |
倍增表和长度为
此外,如果不考虑倍增表,我们依然可以利用事实1从而达到在
-
考虑一个最坏情况,将长度为
$1..n$ 的链摆成一排,将每条链的端点依次相连,那么将会有一个节点到根的路径上有$n - 1$ 条轻边,而此时树上有$n(n + 1) / 2$ 个节点。故上界至少是$\Omega(\sqrt n)$ ↩