【最近公共祖先】学习&总结

前言

  1. 回顾,acm生涯快要结束了,cf终究没有上*1900。不过拿牌应该是ok的,最后一段时间,好好巩固基础知识打好基础!!!&(适当做一些难题拓拓上限)
  2. 放弃了学习Tarjan等更多解决LCA问题的算法,选择了刷题emmm。倍增不能解决再说吧!

性质

  1. 如果 u u u不为 v v v的祖先并且 v v v不为 u u u的祖先,那么 u , v u,v u,v分别处于LCA( u , v u,v u,v)的两棵不同子树中。
  2. 前序遍历中,LCA( S S S)出现在所有 S S S中元素之前,后序遍历中LCA( S S S)则出现在所有 S S S中元素之后。
  3. 两点集并的最近公共祖先为两点集分别的最近公共祖先的最近公共祖先,即 L C A ( A ∩ B = L C A ( L C A ( A ) , L C A ( B ) ) ) LCA(A\cap B=LCA(LCA(A),LCA(B))) LCA(AB=LCA(LCA(A),LCA(B)))
  4. 两点的最近公共祖先必定处在树上两点间的最短路上。
  5. d ( u , v ) = h ( u ) + h ( v ) − 2 h ( L C A ( u , v ) ) d(u,v)=h(u)+h(v)-2h(LCA(u,v)) d(u,v)=h(u)+h(v)2h(LCA(u,v)),其中 d d d是树上两点间的距离, h h h代表某点到树根的距离。

求法1:朴素算法(单词查询随机复杂度为 O ( log ⁡ n ) O(\log n) O(logn),但是可以很容易卡到 O ( n ) O(n) O(n)

  1. 每次深度较大的向上跳(一步一步向上跳),知道两个点相遇。

求法2:倍增算法(最经典的LCA求法&朴素算法的倍增改进算法&预处理复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn),单次查询复杂度 O ( log ⁡ n ) O(\log n) O(logn)

  1. 设最开始 u , v u,v u,v两点深度相差为 y y y,先对 y y y进行二进制拆分,使两点深度一致。
  2. 然后从高位到地位不断尝试,如果f[u][i]!=f[v][i]就跳,否则不跳。循环结束即得到LCA(u,v)的两个儿子,最后处理以下即可(fa[u][0])。
  3. **小知识:**另外倍增算法可以通过交换fa数组的两维使较小维放在前面。这样可以减小chche miss次数,提高程序效率。

模板(知道原理很简单,以下一遍过)

// 其中fa[i][0]在dfs时求出
void init_LCA() {
    
    
    for (int j = 1; j <= 20; j++) {
    
    
        for (int i = 1; i <= n; i++) {
    
    
            fa[i][j] = fa[fa[i][j - 1]][j - 1];
        }
    }
}
//返回的是LCA节点
int LCA(int x, int y) {
    
    
    if (dep[x] < dep[y]) swap(x, y);
    int diff = dep[x] - dep[y];
    for (int j = 20; j >= 0; j--)
        if (diff >= (1 << j))
            x = fa[x][j], diff -= (1 << j);  //差点忘了diff-=(1<<j)
    if (x == y) return x;//这里也要注意,不然就是fa[LCA(x,y)][0]了
    for (int j = 20; j >= 0; j--)
        if (fa[x][j] != fa[y][j]) x = fa[x][j], y = fa[y][j];
    return fa[x][0];
}

题目1:查询树上两点的距离(性质5: d i s t [ u , v ] = d i s t [ u ] + d i s t [ v ] − 2 ∗ d i s t [ L C A ( u , v ) ] dist[u,v]=dist[u]+dist[v]-2*dist[LCA(u,v)] dist[u,v]=dist[u]+dist[v]2dist[LCA(u,v)]

  1. 传送门How far away ? HDU - 2586
  2. 题意:不超过10组样例,一个村庄 n 户人,有 n-1 条无向边连接 n 户人,每组样例不超过100次询问,询问两户人家的距离。
    1. 1 ≤ n ≤ 40000 , 0 ≤ 边 ≤ 40000 1\le n\le 40000,0\le 边\le 40000 1n40000040000
  3. 题解:略
  4. 代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#define int long long
#define pii pair<int, int>
#define x first
#define y second
#define dbg(x) cout << #x << "===" << x << endl
using namespace std;
template <class T>
void read(T &x) {
    
    
    T res = 0, f = 1;
    char c = getchar();
    while (!isdigit(c)) {
    
    
        if (c == '-') f = -1;
        c = getchar();
    }
    while (isdigit(c)) res = (res << 3) + (res << 1) + (c - '0'), c = getchar();
    x = res * f;
}
const int N = 4e4 + 100;
int n, q, u, v, w;
vector<pii> g[N];
int dep[N], fa[N][23], dis[N];
void init() {
    
    
    for (int i = 1; i <= n; i++) g[i].clear();
    memset(fa, 0, sizeof(fa));
    memset(dep, 0, sizeof(dep));
    memset(dis, 0, sizeof(dis));
}
void dfs(int x, int Fa) {
    
    
    for (auto i : g[x]) {
    
    
        if (i.x == Fa) continue;
        dis[i.x] = dis[x] + i.y;
        dep[i.x] = dep[x] + 1;
        fa[i.x][0] = x;
        dfs(i.x, x);
    }
}
void init_LCA() {
    
    
    for (int j = 1; j <= 20; j++) {
    
    
        for (int i = 1; i <= n; i++) {
    
    
            fa[i][j] = fa[fa[i][j - 1]][j - 1];
        }
    }
}
int LCA(int x, int y) {
    
    
    if (dep[x] < dep[y]) swap(x, y);
    int diff = dep[x] - dep[y];
    for (int j = 20; j >= 0; j--)
        if (diff >= (1 << j))
            x = fa[x][j], diff -= (1 << j);  //差点忘了-=(1<<j)
    if (x == y) return x;
    for (int j = 20; j >= 0; j--)
        if (fa[x][j] != fa[y][j]) x = fa[x][j], y = fa[y][j];
    return fa[x][0];
}
signed main() {
    
    
    int T;
    read(T);
    while (T--) {
    
    
        read(n), read(q);
        init();
        for (int i = 1; i < n; i++) {
    
    
            read(u), read(v), read(w);
            g[u].push_back({
    
    v, w}), g[v].push_back({
    
    u, w});
        }
        dis[1] = 0, dep[1] = 0;
        dfs(1, -1);
        //为什么初始化为0,就ok?请看LCA部分,只要不影响LCA部分就没问题。
        init_LCA();
        int ans = 0;
        while (q--) {
    
    
            read(u), read(v);
            ans = dis[u] + dis[v] - 2 * dis[LCA(u, v)];
            // cout<<">>>>>>>>>>";
            printf("%lld\n", ans);
        }
    }
    return 0;
}
/*
样本输入
2
3 2
1 2 10
3 1 15
1 2
2 3

2 2
1 2 100
1 2
2 1
样本输出
10
25
100
100
*/

猜你喜欢

转载自blog.csdn.net/I_have_a_world/article/details/120628557