Martian148's blog Martian148's blog
首页
  • ICPC 算法笔记
  • ICPC 算法题解
  • 体系结构
  • 高等数学
  • 线性代数
  • 概率论与数理统计
  • 具体数学
  • Martian148的奇思妙想
  • 游记
  • 通识课笔记
关于
  • useful 网站
  • 友情链接
  • 分类
  • 归档

Martian148

一只热爱文科的理科生
首页
  • ICPC 算法笔记
  • ICPC 算法题解
  • 体系结构
  • 高等数学
  • 线性代数
  • 概率论与数理统计
  • 具体数学
  • Martian148的奇思妙想
  • 游记
  • 通识课笔记
关于
  • useful 网站
  • 友情链接
  • 分类
  • 归档
  • 线上赛板子(实时更新)
  • 数据结构

    • 堆
    • 并查集
    • 树状数组
    • 分块
    • 树相关
    • 线段树
    • 平衡树
    • 树链剖分
    • LCT
    • 最近公共祖先
      • 倍增算法
        • 算法实现
        • 代码实现
      • 用欧拉序转化成RMQ问题
        • 代码实现
    • 虚树
    • 树分治
    • K-D Tree
    • 笛卡尔树
    • 珂朵莉树
  • 数学

  • 计算几何

  • 动态规划

  • 图论

  • 字符串

  • 杂项

  • 算法笔记
  • 数据结构
martian148
2024-09-03
目录

最近公共祖先

# 最近公共祖先

最近公共祖先简称 LCA(Lowest Common Ancestor)。两个节点的最近公共祖先,就是这两个点的公共祖先里面,离根最远的那个。 为了方便,我们记某点集 S={v1,v2,…,vn}S=\{v_1,v_2,\ldots,v_n\}S={v1​,v2​,…,vn​} 的最近公共祖先为 LCA(v1,v2,…,vn)或LCA(S)\text{LCA}(v_1,v_2,\ldots,v_n) 或 \text{LCA}(S)LCA(v1​,v2​,…,vn​)或LCA(S)

# 倍增算法

倍增求 LCA 是对朴素算法的改进,通过预处理 fa[x][i]fa[x][i]fa[x][i] 数组快速向上跳。fa[x][i]fa[x][i]fa[x][i] 表示点 xxx 的第 2i2^i2i 个祖先。fa[x][i]fa[x][i]fa[x][i] 可以通过 DFS 预处理出来

# 算法实现

  1. 求出每个点的深度
  2. 将 u,vu,vu,v 跳到同一深度。先计算出 u,vu,vu,v 两点的深度之差,设其为 yyy。通过将 yyy 二进制拆分,每次跳 yyy 二进制下为 111 的 2i2^i2i
  3. 将 u,vu,vu,v 同时向上跳,如果 fa[u][i]≠fa[v][i]fa_[u][i] \neq fa[v][i]fa[​u][i]​=fa[v][i] ,则u←fa[u][i]u\gets\text{fa}[u][i]u←fa[u][i],v←fa[v][i]v\gets\text{fa}[v][i]v←fa[v][i]
  4. 最后的 LCA 为 fa[u][0]fa[u][0]fa[u][0]

倍增预处理复杂度为 O(nlog⁡n)O(n\log n)O(nlogn) ,单词查询时间复杂度为 O(log⁡n)O(\log n)O(logn)

另外倍增算法可以通过交换 fa 数组的两维使较小维放在前面。这样可以减少 cache miss 次数,提高程序效率

# 代码实现

#include <bits/stdc++.h>
using namespace std;

const int maxn = 5e5 + 5;
int n, m, rt;
vector<int> g[maxn];
int f[maxn][25], dep[maxn];

void dfs(int u, int fa) {
    f[u][0] = fa;
    dep[u] = dep[fa] + 1;
    for (int i = 1; i <= 20; i++)
        f[u][i] = f[f[u][i - 1]][i - 1];
    for (auto v : g[u]) {
        if (v == fa) continue;
        dfs(v, u);
    }
}

int lca(int x, int y) {
    if (dep[x] < dep[y]) swap(x, y);
    for (int i = 20; i >= 0; i--)
        if (dep[f[x][i]] >= dep[y])
            x = f[x][i];
    if (x == y) return x;
    for (int i = 20; i >= 0; i--)
        if (f[x][i] != f[y][i])
            x = f[x][i], y = f[y][i];
    return f[x][0];
}

int main() {
    ios::sync_with_stdio(false);
    cin >> n >> m >> rt;
    for (int i = 1; i < n; i++) {
        int x, y; cin >> x >> y;
        g[x].push_back(y);
        g[y].push_back(x);
    }
    dfs(rt, 0);
    while (m--) {
        int x, y; cin >> x >> y;
        cout << lca(x, y) << endl;
    }
    return 0;
}
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# 用欧拉序转化成RMQ问题

对一棵树进行 DFS,无论是第一次访问还是回溯,每次到达一个结点时都将编号记录下来,可以得到一个长度为 2n−12n-12n−1 的序列,这个序列被称作这棵树的欧拉序列

image.png

图中的欧拉序为

1→2→4→7→4→8→4→2→5→2→1→3→6→3→1 1 \rightarrow 2 \rightarrow 4 \rightarrow 7 \rightarrow 4 \rightarrow 8 \rightarrow 4 \rightarrow 2 \rightarrow 5 \rightarrow 2 \rightarrow 1 \rightarrow 3 \rightarrow 6 \rightarrow 3 \rightarrow 1 1→2→4→7→4→8→4→2→5→2→1→3→6→3→1

我们设节点 uuu 在欧拉序中第一次出现的位置编号记为 pos(u)pos(u)pos(u) ,把欧拉序列记为 {E}\{E\}{E},通过欧拉序,我们可以把 LCALCALCA 转化成 RMQ 问题

LCA(u,v)LCA(u,v)LCA(u,v) 就是 E[pos(u)∼pos(v)]E[pos(u)\sim pos(v)]E[pos(u)∼pos(v)] 中深度最小的点

用 RMQRMQRMQ 很容易实现区间最值问题

# 代码实现

#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 5;

vector<int> g[maxn];

int dep[maxn << 1], lg[maxn << 1], st[maxn << 1][25], dfn[maxn], tot;
int E[maxn << 1];

void dfs (int u, int fa) {
    dfn[u] = ++tot; E[tot] = u;
    dep[u] = dep[fa] + 1;
    for (auto v : g[u]) {
        if (v == fa) continue;
        dfs(v, u);
        E[++tot] = u;
    }
}

void build_st() {
    lg[0] = -1;
    for (int i = 1; i <= tot; i++) lg[i] = lg[i >> 1] + 1;

    for (int i = 1; i <= tot; i++) st[i][0] = E[i];
    for (int j = 1; j <= 20; j++) {
        for (int i = 1; i + (1 << j) - 1 <= tot; i++) {
            int x = st[i][j - 1], y = st[i + (1 << (j - 1))][j - 1];
            st[i][j] = dep[x] < dep[y] ? x : y;
        }
    }
}

int lca (int u, int v) {
    if (dfn[u] > dfn[v]) swap(u, v);
    int l = dfn[u], r = dfn[v];
    int k_ = lg[r - l + 1];
    int x = st[l][k_], y = st[r - (1 << k_) + 1][k_];
    return dep[x] < dep[y] ? x : y;
}

int main() {
    freopen ("P3379.in", "r", stdin);
    ios::sync_with_stdio(false);
    int n, m, rt; cin >> n >> m >> rt;
    for (int i = 1; i < n; i++) {
        int x, y; cin >> x >> y;
        g[x].push_back(y);
        g[y].push_back(x);
    }

    dfs(rt, 0);
    build_st();
    while (m--) {
        int x, y; cin >> x >> y;
        cout << lca(x, y) << endl;
    }
    return 0;
}
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
上次更新: 2025/04/08, 18:03:31
LCT
虚树

← LCT 虚树→

最近更新
01
Java基础语法
05-26
02
开发环境配置
05-26
03
pink 老师 JavaScript 学习笔记
05-26
更多文章>
Theme by Vdoing | Copyright © 2024-2025 Martian148 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式