求LCA(倍增/DFS序/重链剖分)Go语言
本博客用于记录板子
- 1、倍增算法
- 2、DFS序求LCA
- 3、重链剖分
学习算法推荐: 灵茶山艾府
1、倍增算法
O ( n log n ) O(n \log n) O(nlogn)预处理, O ( log n ) O(\log n) O(logn)查询
讲解看:树的第K个祖先
练习题:P3379 【模板】最近公共祖先(LCA)
直接贴下面代码会超时,这与bufio
有关,换成快读就过了
代码:
package mainimport ("bufio". "fmt""math/bits""os"
)func main() {in, out := bufio.NewReader(os.Stdin), bufio.NewWriter(os.Stdout)defer out.Flush()var n, m, s, x, y intFscan(in, &n, &m, &s)g := make([][]int, n+1) // 数据量大的时候,bufio读取数据有问题,这里最好写成5e5+1for i := 0; i < n-1; i++ {Fscan(in, &x, &y)g[x] = append(g[x], y)g[y] = append(g[y], x)}mx := bits.Len(uint(n))pa := make([][]int, n+1)for i := range pa {pa[i] = make([]int, mx)for j := range pa[i] {pa[i][j] = -1}}dep := make([]int, n+1)var dfs func(int, int)dfs = func(u, p int) {dep[u] = dep[p] + 1pa[u][0] = pfor _, ne := range g[u] {if ne == p {continue}dfs(ne, u)}}dfs(s, 0)for i := 0; i < mx-1; i++ {for x := 1; x <= n; x++ {if p := pa[x][i]; p >= 0 {pa[x][i+1] = pa[p][i]} else {pa[x][i+1] = -1}}}uptoDep := func(x, d int) int {for k := uint(dep[x] - d); k > 0; k &= k - 1 {x = pa[x][bits.TrailingZeros(k)]}return x}lca := func(x, y int) int {if dep[x] < dep[y] {x, y = y, x}x = uptoDep(x, dep[y])if x == y {return x}for i := mx - 1; i >= 0; i-- {if pa[x][i] != pa[y][i] {x, y = pa[x][i], pa[y][i]}}return pa[x][0]}for i := 0; i < m; i++ {Fscan(in, &x, &y)Fprintln(out, lca(x, y))}
}
2、DFS序求LCA
为什么用DFS序求LCA?
优点: O ( n log n ) O(n \log n) O(nlogn)预处理, O ( 1 ) O(1) O(1)查询,适用于大量查询的情况
讲解看 冷门科技 —— DFS 序求 LCA
核心思想:
dfs序是按照深度优先遍历得到的序列,dfn[x]是节点x在dfs序中的下标。
对于成祖辈节点的结构来说,祖宗节点的dfn要小于子节点的dfn
对于两个节点x, y
,
1、如果二者形成下述格式 (旁边数字是dfn)
这种情况下X
一定不是LCA,且dfn值在 [ d f n [ x ] , d f n [ y ] ] [dfn[x], dfn[y]] [dfn[x],dfn[y]]的节点一定在D
的子树中。
考虑Y
分支上D
的第一个儿子节点Z
, Z
的父亲节点一定是X, Y
的LCA
进一步的, [ d f n [ x ] + 1 , d f n [ y ] ] [dfn[x] + 1, dfn[y]] [dfn[x]+1,dfn[y]]深度最小的节点的父节点一定是LCA
2、如果二者形成下述格式
此时X
一定是X,Y
的LCA, 进一步的讲, [ d f n [ x ] + 1 , d f n [ y ] ] [dfn[x] + 1, dfn[y]] [dfn[x]+1,dfn[y]]深度最小的节点的父节点一定是LCA
没有上述两个情况之外的例子。
那么问题变成了求 [ d f n [ x ] + 1 , d f n [ y ] ] [dfn[x] + 1, dfn[y]] [dfn[x]+1,dfn[y]]深度最小节点的父节点。这可以用ST表来维护
代码:
package mainimport ("bufio". "fmt""math/bits""os"
)func main() {in, out := bufio.NewReader(os.Stdin), bufio.NewWriter(os.Stdout)defer out.Flush()var n, m, s, x, y intFscan(in, &n, &m, &s)g := make([][]int, n+1)for i := 0; i < n-1; i++ {Fscan(in, &x, &y)g[x] = append(g[x], y)g[y] = append(g[y], x)}t := bits.Len(uint(n))st := make([][]int, n+1) // st[i][j]表示 [i, i + 2^j)深度最小节点的父节点for i := range st {st[i] = make([]int, t)}clock := 0dfn := make([]int, n+1)var dfs func(int, int)dfs = func(u, p int) {clock++dfn[u] = clockst[clock][0] = pfor _, ne := range g[u] {if ne != p {dfs(ne, u)}}}dfs(s, 0)op := func(a, b int) int {if dfn[a] < dfn[b] {return a}return b}for j := 1; 1<<j <= n; j++ {for i := 1; i+1<<j <= n+1; i++ {st[i][j] = op(st[i][j-1], st[i+1<<(j-1)][j-1])}}lca := func(u, v int) int {if u == v {return u}if dfn[u] > dfn[v] {u, v = v, u}left := dfn[u] + 1right := dfn[v]d := bits.Len(uint(right+1-left)) - 1// [left, right], st右侧是开区间,所以下面是right+1return op(st[left][d], st[right+1-1<<d][d])}for i := 0; i < m; i++ {Fscan(in, &x, &y)Fprintln(out, lca(x, y))}
}
3、重链剖分
推荐观看:重链剖分
没有基础也不看上面推荐视频的话,下面代码是看不懂的
核心思路: 如果x, y
不在同一个链上,那么让链头深度较小的节点向上跳。
如果在一个链上,返回深度较小的节点。
package mainimport ("bufio". "fmt""os"
)func main() {in, out := bufio.NewReader(os.Stdin), bufio.NewWriter(os.Stdout)defer out.Flush()var n, m, s, x, y intFscan(in, &n, &m, &s)g := make([][]int, n+1)for i := 0; i < n-1; i++ {Fscan(in, &x, &y)g[x] = append(g[x], y)g[y] = append(g[y], x)}son, fa, dep, sz := make([]int, n+1), make([]int, n+1), make([]int, n+1), make([]int, n+1)var dfs1 func(int, int)dfs1 = func(u, p int) {dep[u] = dep[p] + 1sz[u] = 1fa[u] = pfor _, ne := range g[u] {if ne != p {dfs1(ne, u)if sz[ne] > sz[son[u]] {son[u] = ne}sz[u] += sz[ne]}}}// todo: dfn, rank在这里用不到,这里只是为了HLD的完整性dfn, top, rank := make([]int, n+1), make([]int, n+1), make([]int, n+1)clock := 0var dfs2 func(int, int)dfs2 = func(u, tp int) {clock++dfn[u] = clocktop[u] = tprank[clock] = uif son[u] == 0 {return}dfs2(son[u], tp)for _, ne := range g[u] {if ne == fa[u] || ne == son[u] {continue}dfs2(ne, ne) // 新开一个作为链头}}dfs1(s, 0)dfs2(s, s)lca := func(x, y int) int {for top[x] != top[y] {// 保证x所在链头是深度较大的if dep[top[x]] < dep[top[y]] {x, y = y, x}x = fa[top[x]]}// 在同一条链上if dep[x] < dep[y] {return x}return y}for i := 0; i < m; i++ {Fscan(in, &x, &y)Fprintln(out, lca(x, y))}
}
运行结果: