DAG 链剖分

类比树链剖分。

DAG 链剖分希望将若干条边定为轻边,使得每条路径包含的轻边个数比较少。

构造:记 \(f_i\) 为以 \(i\) 结尾的路径数,\(g_i\) 为以 \(i\) 开头的路径数。边 \(u\to v\) 为重边当且仅当 \(u\)\(v\) 的入点中 \(f\) 最大的同时 \(v\)\(u\) 的出点中 \(g\) 最大的。

观察一下。不难发现每个点的出入重边条数 \(\le 1\),所以肯定形如若干条链;同时每次走过一条轻边,\(f,g\) 至少一个减半。因此路径上的轻边条数是 \(O(\log V)\) 的:\(V\) 为这张 DAG 的总路径条数。

最常见甚至说唯一的应用场景是 SAM。

众所周知,我们可以快速在 SAM 上找到一个字符串,并且可以用一个节点在 parent 树上到根节点的路径表示一个字符串的所有后缀,但是无法很方便地表示一个字符串的所有前缀。

考虑一个字符串的所有前缀在 SAM 上对应的点是什么。

由于在一个字符串末尾添加字符相当于在 SAM 的 DAG 上走一条边,所以一个字符串的所有前缀对应了 DAG 上的一条路径。

SAM 的 DAG 上,总路径条数也不多:显然小于 \(O(n^3)\)。所以 \(O(\log V)=O(\log n)\)


一些基本的实现和树剖类似。

先跑 DP 求出 \(f,g\) 以及重边,然后从初始点搜一遍,优先走重边并编号,这样每条重链上的编号就都是连续的,每条路径就可以写作 \(O(\log n)\) 个区间的并。

怎么把一个子串对应的路径的 \(O(\log n)\) 个区间拆出来呢?

首先,定位出 \(s[l:r]\) 的结点 \(x\)\(s[1:r]\) 再倍增跳 parent 树)。
我们要从 \(x\) 出发,依次走过 \(s[l:r]\) 这些字符的边。

设我们当前位于结点 \(u\),还要走过 \(s[p:r]\)

  • \(u\)\(s[p]\) 出边是轻边,直接走。

  • 否则我们要找到 \(s[p]\) 后续的重链与 \(s[p:r]\) 的 LCP。

    注意重链也是路径,也对应某个子串。对每个结点预处理出它到它的重链末端,对应的子串 \(s[a:b]\)

    那么我们相当于求两个后缀的 LCP,可以二分哈希,也可以 SA。

    找到之后直接加上 LCP 的长度即可跳过这条重链。

复杂度视使用 SA 还是二分哈希为 \(O(\log n)\) 或 $ O(\log^2n)$。


来看看 sam 套 dag 链剖分可以做什么。

  • 【qoj897 基本子串字典】求区间 border 个数。

考虑一个前缀的 border 的条件,即 \(s[l:i]\)\(s[l:r]\) 的后缀。

后缀关系在 sam 上刻画为 parent 树的祖孙关系。

\(x\)\(s[1:r]\) 对应的结点,把 \(s[l:r]\) 的所有前缀的结点用上面的方法拆出 \(O(\log n)\) 个区间之后,相当于询问 \(O(q\log n)\) 次:

每次给定一个区间以及一个结点 \(x\)。求 parent 树上 \((root,x)\) 路径上有多少个结点的编号落在给定区间内。

本质二维数点。离线后对 parent 树做 dfs,同时用一个 BIT 维护结点编号即可。

复杂度 \(O(q\log^2n)\)

同理这个做法也支持查询最短、最长 border。只是变成查询区间最短、最长而已。

  • 【uoj752 Border 的第五种求法】懒得形式化题面了

类似 dag 链剖分后也是 \(O(q\log n)\) 次二维数点。

  • 【CF1098F Ж-function】求区间所有后缀与区间的 LCP 长度求和。

这个是可以做的,不过本题用 SA 做比较正常而且常数更小,要看去看洛谷题解区里有用 dag 链剖分做的


暂时没发现在 SAM 以外的应用。可能是 "总路径条数不多" 这个限制,SAM 结合得最优美,而且人为构造一个图基本除了总路径条数不多以外还有一些过强的性质导致根本不需要使用 dag 链剖分。

posted @ 2026-08-19 22:04  FLY_lai  阅读(26)  评论(1)    收藏  举报