2025.7.22 图论

长链剖分前置知识

最好有重链剖分的基础再来学这个,否则你应当先学习重剖.

类似于重链剖分以子树大小最大的作为重儿子,长链剖分以儿子中拥有最长链的为重儿子. 所以我们仿照重剖,可以写出两个核心 dfs 函数.

int len[maxn], son[maxn], fa[maxn];
inline void dfs1(int u, int f) {
	fa[u] = f, len[u] = 1;
	for(int i = head[u], v; i; i = e[i].nxt) {
		v = e[i].v; if(v == f) continue;
		dfs1(v, u);
		if(len[v] + 1 > len[u]) len[u] = len[v] + 1, son[u] = v;
	} return;
}
int tp[maxn];
inline void dfs2(int u, int t) {
	tp[u] = t; if(son[u]) dfs2(son[u], t);
	for(int i = head[u], v; i; i = e[i].nxt) {
		v = e[i].v; if(v == fa[u] || v == son[u]) continue;
		dfs2(v, v);
	} return;
}

上述代码维护了 len[u] 表示以 \(u\) 起始的链的长度.

那么长链剖分有什么用呢?我们先来看看它的一些性质.

  • 所有长链长度之和是 \(O(n)\) 的. 该命题相当于所有长链无交,根据定义这是恒成立的.

  • 任意节点 \(u\)\(k\) 级祖先所在的长链长度一定大于等于 \(k\).\(u\)\(k\) 级祖先是 \(v\),分类讨论. 如果 \(u\)\(v\) 在同一条长链显然成立;如果 \(u\)\(v\) 不在同一条长链上,假设 \(v\) 所在长链长度小于 \(k\),但是 \(v\rightarrow u\) 这条链长度已经为 \(k\),所以长链应该延申到 \(u\),这与 \(u,v\) 不在同一条长链相矛盾,故假设不成立.

  • 任意节点 \(u\) 可以经过 \(O(\sqrt n)\) 条轻边到达根节点. 该命题相当于跳 \(O(\sqrt n)\) 次长链顶可以到达根节点. 根据第一条性质,我们会发现向上跳长链的长度是严格单调递增的,否则一定可以通过调整得到更长的链作为长链. 故跳的长链长度之和至少是 \(1+2+\cdots=n\),会发现长链条数是 \(O(\sqrt n)\) 的.

P5903 【模板】树上 K 级祖先

Hint:利用上述的第一、二条性质,把询问复杂度降到 \(O(1)\).

类似于 LCA 的,我们考虑倍增,预处理出每个节点向上跳 \(2^i\) 步可以到达的节点. 但是为了 \(O(1)\) 询问我们显然不能一直跳,考虑结合长剖来进行一些优化. 现在我们可以向上跳 \(O(1)\)\(2^i\) 或向上跳 \(O(1)\) 次链顶,这似乎是不足以让我们直接找到 \(k\) 级祖先. 根据性质一,我们其实可以预处理出链顶向上、向下跳链长内可以到达的点,时空复杂度 \(O(n)\). 下面我们将证明,可以将 \(k\) 拆分为至多 \(3\) 次跳来得到.

先向上跳 \(2^{\lfloor\log_2k\rfloor}\) 步,再向上跳 dep[u]-dep[tp[u]] 步到达链顶. 由于先向上跳了一次至少使 \(k\) 减半(特判 \(k=0\)),根据性质二,此时 \(u\) 所在长链的长度应大于 \(2^{\lfloor\log_2k\rfloor}\),进一步大于剩下的 \(k'=k-2^{\lfloor\log_2k\rfloor}\). 由于长链长度大于 \(k'\),所以我们跳到链顶之后 \(k\) 级祖先一定在上下至多链长内,直接查找预处理的点即可.

P10641 BZOJ3252 攻略

Hint:等价于 \(k\) 条不交的最长路径,路径经过调整一定可以使叶子节点作为端点,考虑数据结构优化.

修改链长的定义为链上点权之和,会发现长剖刚好满足这些要求.

于是跑长剖,把链顶的每条链拿出来排序取前 \(k\) 大求和即可.

posted @ 2025-07-25 14:58  Ydoc770  阅读(19)  评论(0)    收藏  举报