Chgtaxihe's blog Chgtaxihe's blog
首页
练习
算法学习
读书笔记
小玩意

Chgtaxihe

首页
练习
算法学习
读书笔记
小玩意
  • 数论

  • 动态规划

  • 暴力

  • 图论

    • 2-SAT学习笔记
    • 最短路学习笔记
    • KM学习笔记
    • 一般图最大匹配学习笔记
    • 二分图匹配学习笔记
    • 可撤销并查集学习笔记
    • 多源最短路学习笔记
    • 斯坦纳树学习笔记
    • 最小异或生成树学习笔记
    • LCA学习笔记
      • 洛谷 P3379 (2.13s, 69.81MB)
      • 洛谷 P3379 (1.89s, 41.66MB)
      • 洛谷 P3379 (1.27s, 23.75MB)
    • 树的直径与重心
    • 树的重心
    • 矩阵树定理学习笔记
    • 网络流-最小割学习笔记
    • 网络流-最小费用最大流学习笔记
  • 数据结构

  • 字符串

  • 几何

  • 博弈论

  • 其他

LCA学习笔记

# 最近公共祖先

定义:在一棵以rrr为根的树上的两个点(u,v)(u,v)(u,v),其最近公共祖先为u,vu,vu,v各自到根的路径中,深度最大的公共节点

# 倍增算法(在线)

# 洛谷 P3379 (2.13s, 69.81MB)

用邻接表存图会快很多!

#include <bits/stdc++.h>
#define Android ios::sync_with_stdio(false), cin.tie(NULL)
using namespace std;

const int maxn = 5e5 + 1000;

const int max_lgn = 20;
int lg[maxn], depth[maxn], up[maxn][max_lgn];
vector<int> G[maxn];
  
void build(int u, int fa, int dep){
    depth[u] = dep;
    for(int i=1; i<max_lgn; i++)
        up[u][i] = up[up[u][i-1]][i-1]; // 倍增
    for(int v:G[u]){
        if(v == fa) continue;
        up[v][0] = u;
        build(v, u, dep + 1);
    }
}

int query(int u, int v){
    if(depth[u] < depth[v]) swap(u, v);

    while(depth[u] > depth[v])
        u = up[u][lg[depth[u] - depth[v]] - 1];
    if(u == v) return u;

    for(int k = lg[depth[u]]; k >= 0; k--){
        if(up[u][k] != up[v][k])
            u = up[u][k], v = up[v][k];
    }
    return up[u][0];
}

void solve(){
    for(int i=1; i<maxn; i++)
        lg[i] = lg[i-1] + (1<<lg[i-1]==i); // log_2(i) + 1

    int n, m, root;
    cin >> n >> m >> root;

    for(int i=1, u, v; i<n; i++){
        cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
    }

    build(root, 0, 1);

    for(int i=0, u, v; i<m; i++){ // m 个询问
        cin >> u >> v;
        cout << query(u, v) << '\n';
    }
}
  
signed main(){
    Android;
    solve();
}
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
59
60

# Tarjan(离线)

参考资料 (opens new window)

算法步骤如下:

  1. 递归遍历当前节点uuu的所有子节点
  2. 检查查询中,与当前节点uuu有关的询问(u,v)(u,v)(u,v),若vis[v]=truevis[v]=truevis[v]=true,则lca(u,v)=getfa(v)lca(u,v)=getfa(v)lca(u,v)=getfa(v)(getfa(x)getfa(x)getfa(x)为并查集的函数)
  3. 标记当前点,即令vis[u]=truevis[u]=truevis[u]=true,同时,在并查集上将当前节点uuu与其父节点合并

没有太多需要注意的细节

# 洛谷 P3379 (1.89s, 41.66MB)

不能在线处理,但胜在代码简单

Ps. 查询也可以使用邻接表储存

#include <bits/stdc++.h>
#define Android ios::sync_with_stdio(false), cin.tie(NULL)
#define pii pair<int, int>  
using namespace std;

const int maxn = 5e5 + 1000;
int n, m, root;
vector<pii> query[maxn];
int head[maxn], nxt[2 * maxn], dest[2 * maxn], edge_cnt = 0;
int ans[maxn];
int union_set[maxn], vis[maxn];

int get_fa(int x){
    return union_set[x] == x?x:union_set[x] = get_fa(union_set[x]);
}

void add_edge(int u, int v){
    nxt[++edge_cnt] = head[u];
    dest[edge_cnt] = v;
    head[u] = edge_cnt;
}

void tarjan(int u, int fa){
    for(int x=head[u]; x; x=nxt[x]){
        if(dest[x] == fa) continue;
        tarjan(dest[x], u);
        union_set[get_fa(dest[x])] = u;
    }
    for(pii q:query[u]){
        if(!vis[q.second]) continue;
        ans[q.first] = get_fa(q.second);
    }
    vis[u] = true;
}

void solve(){
    cin >> n >> m >> root;

    memset(vis, 0, sizeof vis);
    for(int i=0; i<=n; i++) union_set[i] = i;

    for(int i=1, u, v; i<n; i++){
        cin >> u >> v;
        add_edge(u, v);
        add_edge(v, u);
    }

    for(int i=0, u, v; i<m; i++){
        cin >> u >> v;
        query[u].push_back({i, v});
        query[v].push_back({i, u});
    }

    tarjan(root, 0);
    for(int i=0; i<m; i++) cout << ans[i] << '\n';
}

signed main(){
    Android;
    solve();
}
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
59
60
61

# 树链剖分(在线)

没想到树剖居然这么快!

但能不剖就不剖

# 洛谷 P3379 (1.27s, 23.75MB)

#include <bits/stdc++.h>
#define Android ios::sync_with_stdio(false), cin.tie(NULL)
using namespace std;

struct heavy_light_seg{
    static const int max_node = 5e5 + 100;
    // 储存原图
    int head[max_node], nxt[max_node * 2], dest[max_node * 2];
    int n, edge_cnt, root;
    // 原图信息
    int size[max_node], fa[max_node], heavy_son[max_node], depth[max_node];
    // 重链剖分
    int dfn;
    int top[max_node], id_node[max_node], node_id[max_node];

    void init(int node_cnt){
        n = node_cnt;
        edge_cnt = dfn = 0;
        for(int i=0; i<=n; i++) head[i] = 0, heavy_son[i] = -1;
    }

    inline void add_edge(int u, int v){ // 原图的边
        dest[++edge_cnt] = v;
        nxt[edge_cnt] = head[u];
        head[u] = edge_cnt;
    }

    void _dfs1(int u, int parent, int dep){
        size[u] = 1;
        fa[u] = parent;
        depth[u] = dep;
        int & hs = heavy_son[u];
        for(int x=head[u]; x; x=nxt[x]){
            if(dest[x] == parent) continue;
            _dfs1(dest[x], u, dep + 1);
            size[u] += size[dest[x]];
            if(hs == -1 || size[hs] < size[dest[x]]) hs = dest[x];
        }
    }

    void _dfs2(int u, int t){ // 按dfs序编号
        top[u] = t;
        node_id[u] = ++dfn;
        id_node[dfn] = u;

        if(heavy_son[u] == -1) return; // 叶子节点
        _dfs2(heavy_son[u], t); // 先遍历重子节点,使dfs序连续
        for(int x=head[u]; x; x=nxt[x]){
            if(dest[x] == fa[u] || dest[x] == heavy_son[u]) continue;
            _dfs2(dest[x], dest[x]);
        }
    }

    void build(int rt){
        root = rt;
        _dfs1(root, -1, 1);
        _dfs2(root, root);
    }

    int lca(int u, int v){
        while(top[u] != top[v]){ // 不在同一条链上
            if(depth[top[u]] > depth[top[v]]) swap(u, v);
            v = fa[top[v]];
        }
        return depth[u] < depth[v]?u:v;
    }
}hls;

void solve(){
    int n, m, s;
    cin >> n >> m >> s;
    hls.init(n);
    for(int i=1, u, v; i<n; i++){
        cin >> u >> v;
        hls.add_edge(u, v);
        hls.add_edge(v, u);
    }
    hls.build(s);
    for(int i=0, u, v; i<m; i++){
        cin >> u >> v;
        cout << hls.lca(u, v) << '\n';
    }
}

signed main(){
    Android;
    solve();
}
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
上次更新: 2021/02/24, 03:37:30
最小异或生成树学习笔记
树的直径与重心

← 最小异或生成树学习笔记 树的直径与重心→

最近更新
01
深入理解Java虚拟机
03-04
02
DNS工作原理
02-27
03
改用VuePress啦
02-23
更多文章>
Theme by Vdoing | Copyright © 2019-2021
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式