• 最近公共祖先Lca(ST表,树剖,倍增,Tarjan, LCT)


    A 树链剖分求lca

    • 轻重链剖分,很快,且好写

    Code

    Show Code
    //树链剖分
    #include <cstdio>
    using namespace std;
    const int N = 5e5+5;
    struct Side {
        int t, next;
    }e[N<<1];
    int head[N], tot;
    void Add(int x, int y) {
        e[++tot] = (Side){y, head[x]};
        head[x] = tot;
    }
    int siz[N], f[N], son[N], d[N], top[N];
    void Dfs1(int x) {
        siz[x] = 1;
        d[x] = d[f[x]] + 1;
        for (int i = head[x]; i; i = e[i].next) {
            int y = e[i].t;
            if (y == f[x]) continue;
            f[y] = x;
            Dfs1(y);
            siz[x] += siz[y];
            if (!son[x] || siz[son[x]] < siz[y])
                son[x] = y;
        }
    }
    void Dfs2(int x, int tp) {
        top[x] = tp;
        if (!son[x]) return;
        Dfs2(son[x], tp);
        for (int i = head[x]; i; i = e[i].next) {
            int y = e[i].t;
            if (y == f[x] || y == son[x]) continue;
            Dfs2(y, y);
        }
    }
    int Lca(int x, int y) {
        while (top[x] != top[y])
            d[top[x]] > d[top[y]] ? x = f[top[x]] : y = f[top[y]];
        return d[x] < d[y] ? x : y;
    }
    int n, m, s;
    int main() {
        scanf("%d%d%d", &n, &m, &s);
        for (int i = 1; i < n; i++) {
            int x, y;
            scanf("%d%d", &x, &y);
            Add(x, y); Add(y, x);
        }
        Dfs1(s); Dfs2(s, s);
        while (m--) {
            int x, y;
            scanf("%d%d", &x, &y);
            printf("%d
    ", Lca(x, y));
        }
        return 0;
    }
    



    B Lca转RMQ

    • 利用的是欧拉序的一个特性——任意两个点之间(包括这两点)深度最小的节点就是两个点的最近公共祖先。

    欧拉序:就是从根结点出发,按dfs的顺序在绕回原点所经过所有点的顺序

    • 反证法:

      • 假设深度最小的点不是LCA,那么这个LCA点一定在两点之外。

      • 但是欧拉序在走到任意一个兄弟前,一定要经过两个距离最近的公共点(也就是LCA)

      • 所以假设不成立,证明LCA点一定在两点之间。

      • 证毕。

    • 使用RMQ不是很好写,但是在询问多于点数的时候十分优秀。

    Code

    Show Code
    //RMQ求Lca
    #include <cstdio>
    using namespace std;
    const int N = 5e5 + 5;
    
    #define swap(x, y) {
        int a = x; x = y; y = a;
    }
    
    int read(int x = 0, int f = 1, char c = getchar()) {
        for (; c < '0' || c > '9'; c = getchar())
            if (c == '-') f = -1;
        for (; c >='0' && c <='9'; c = getchar())
            x = x * 10 + c - '0';
        return x * f;
    }
    
    struct Edge {
        int next, t;
    }e[N<<1];
    int head[N], edc;
    
    void Add(int x, int y) {
        e[++edc] = (Edge) {head[x], y};
        head[x] = edc;
    }
    
    int n, m, rt, lg[N<<1], f[21][N<<1];//f是深度最低的节点
    int dep[N], dfn[N], dfc;
    
    void Dfs(int x, int fa) {
        dfn[x] = ++dfc;
        f[0][dfc] = x;
        dep[x] = dep[fa] + 1;
        for (int i = head[x]; i; i = e[i].next) {
            int y = e[i].t;
            if (y == fa) continue;
            Dfs(y, x);
            f[0][++dfc] = x;
        }
    }
    
    int Lca(int x, int y) {
        x = dfn[x]; y = dfn[y];
        if (x > y) swap(x, y);
        int k = lg[y-x+1]; y = y - (1 << k) + 1;
        return dep[f[k][x]] < dep[f[k][y]] ? f[k][x] : f[k][y];
    }
    
    int main() {
        n = read(), m = read(), rt = read();
        for (int i = 1; i < n; ++i) {
            int x = read(), y = read();
            Add(x, y); Add(y, x);
        }
        Dfs(rt, 0);
        //ST表预处理
        for (int i = 2; i <= dfc; ++i)
            lg[i] = lg[i>>1] + 1;
        for (int i = 0; i < lg[dfc]; ++i)
            for (int x = 1; (x + (1 << i + 1)) <= dfc; ++x) {
                int y = x + (1 << i);
                f[i+1][x] = dep[f[i][x]] < dep[f[i][y]] ? f[i][x] : f[i][y];
            }
        while (m--)
            printf("%d
    ", Lca(read(), read()));
        return 0;
    }
    



    C 倍增求Lca

    • f[x][k]表示从x向根节点走(2^k)步到达的节点
      d[x]表示树的深度
      预处理时间复杂度为(O(nlog n)),每次询问时间复杂度为(O(log n))

    Code

    Show Code
    #include <cstdio>
    #include <algorithm>
    
    const int N = 5e5 + 5;
    
    int read(int x = 0, int f = 1, char c = getchar()) {
        for (; c < '0' || c > '9'; c = getchar())
            if (c == '-') f = -1;
        for (; c >='0' && c <='9'; c = getchar())
            x = x * 10 + c - '0';
        return x * f;
    }
    
    struct Edge {
        int next, t;
    }e[N<<1];
    int head[N], edc;
    
    void Add(int x, int y) {
        e[++edc] = (Edge) {head[x], y};
        head[x] = edc;
    }
    
    int n, m, s, f[20][N], dep[N];
    
    void Dfs(int x) {
        dep[x] = dep[f[0][x]] + 1;
        for (int i = 0; (1 << i + 1) <= dep[x]; ++i)
            f[i+1][x] = f[i][f[i][x]];
        for (int i = head[x]; i; i = e[i].next) {
            int y = e[i].t;
            if (y == f[0][x]) continue;
            f[0][y] = x;
            Dfs(y);
        }
    }
    
    int Lca(int x, int y) {
        if (dep[x] < dep[y]) std::swap(x, y);
        for (int k = dep[x] - dep[y], i = 0; k; k >>= 1, ++i)
            if (k & 1) x = f[i][x];
        if (x == y) return x;
        for (int i = 19; i >= 0; --i)
            if (f[i][x] != f[i][y])
                x = f[i][x], y = f[i][y];
        return f[0][x];
    }
    
    int main() {
        n = read(), m = read(), s = read();
        for (int i = 1; i < n; ++i) {
            int x = read(), y = read();
            Add(x, y); Add(y, x);
        }
        Dfs(s);
        while (m--) {
            int x = read(), y = read();
            printf("%d
    ", Lca(x, y));
        }
        return 0;
    }
    



    D tarjan求lca

    • 这种算法本质上是用并查集对向上标记法的优化,是离线算法,即一次性读入所有询问,统一计算,统一输出。
      时间复杂度(O(n+m)),但是常数比较大,可以对并查集部分进行优化。

      • v[]进行标记
        (v[x]doteq 0) --> x节点未访问过
        (v[x]doteq 1) --> x节点已经访问,但未回溯
        (v[x]doteq 2) --> x节点已经访问并回溯

    Code

    Show Code
    #include <cstdio>
    #include <vector>
    #include <algorithm>
    using namespace std;
    const int N = 1e5+5;
    struct side {
        int t, d, next;
    }e[N<<1];
    int head[N], tot;
    void add(int x, int y, int z) {
        e[++tot].next = head[x];
        head[x] = tot;
        e[tot].t = y, e[tot].d = z;
    }
    int n, m, Q, d[N], f[N], v[N], ans[N];
    vector<int> q[N], h[N];
    int found(int x) {
        return x == f[x] ? x : (f[x] = found(f[x]));
    }
    void Dfs(int x) {
        v[x] = 1;
        for (int i = head[x]; i; i = e[i].next) {
            int y = e[i].t;
            if (v[y]) continue;
            d[y] = d[x] + e[i].d;
            Dfs(y);
            f[y] = x;
        }
        for (int i = 0; i < q[x].size(); i++) {
            int y = q[x][i], id = h[x][i];
            if (v[y] != 2) continue;
            ans[id] = min(ans[id], d[x] + d[y] - 2*d[found(y)]);
        }
        v[x] = 2;
    }
    int main() {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++) f[i] = i;
        while (m--) {
            int x, y, z;
            scanf("%d%d%d", &x, &y, &z);
            add(x, y, z); add(y, x, z);
        }
        scanf("%d", &Q);
        for (int i = 1; i <= Q; i++) {
            int x, y;
            scanf("%d%d", &x, &y);
            if (x == y) continue;
            ans[i] = 1 << 30;
            q[x].push_back(y), h[x].push_back(i);
            q[y].push_back(x), h[y].push_back(i);
        }
        Dfs(1);
        for (int i = 1; i <= Q; i++)
            printf("%d
    ", ans[i]);
        return 0;
    }
    



    E LCT求lca

    • 常数巨大,但可以维护动态树的Lca,其他方法都不可以

    Code

    Show Code
    #include <cstdio>
    #include <algorithm>
    #define ls(x) c[x][0]
    #define rs(x) c[x][1]
    #define Get(x) (c[f[x]][1] == x)
    #define Nroot(x) (c[f[x]][Get(x)] == x)
    
    using namespace std;
    const int N = 5e5 + 5;
    
    int read(int x = 0, int f = 1, char c = getchar()) {
        for (; c < '0' || c > '9'; c = getchar())
            if (c == '-') f = -1;
        for (; c >='0' && c <='9'; c = getchar())
            x = x * 10 + c - '0';
        return x * f;
    }
    
    int n, m, rt;
    int f[N], c[N][2], tag[N], stk[N], tp;
    
    void Rotate(int x) {
        int y = f[x], z = f[y], k = Get(x), B = c[x][k^1];
        if (Nroot(y)) c[z][Get(y)] = x; f[x] = z;
        c[x][k^1] = y; f[y] = x; c[y][k] = B; f[B] = y;
    }
    
    void Pushdown(int x) {
        if (!tag[x]) return;
        swap(ls(x), rs(x)); tag[x] = 0;
        tag[ls(x)] ^= 1; tag[rs(x)] ^= 1;
    }
    
    void Splay(int x) {
        for (int y = x; y; y = Nroot(y) ? f[y] : 0) stk[++tp] = y;
        while (tp) Pushdown(stk[tp--]);
        while (Nroot(x)) {
            int y = f[x];
            if (Nroot(y)) Get(x) == Get(y) ? Rotate(y) : Rotate(x);
            Rotate(x);
        }
    }
    
    void Access(int x) {
        for (int y = 0; x; y = x, x = f[x])
            Splay(x), c[x][1] = y;
    }
    
    void Mroot(int x) {
        Access(x); Splay(x); tag[x] ^= 1;
    }
    
    void Link(int x, int y) {
        Mroot(x); f[x] = y;
    }
    
    int Lca(int x, int y) {
        int p = 0; Mroot(rt); Access(y);
        for (; x; p = x, x = f[x])
            Splay(x), c[x][1] = p;
        return p;
    }
    
    int main() {
        n = read(); m = read(); rt = read();
        for (int i = 1; i < n; ++i) Link(read(), read());
        while (m--) printf("%d
    ", Lca(read(), read()));
        return 0;
    }
    
  • 相关阅读:
    统计图配色方案_填充
    如何在C/S下打印报表
    如何利用API导出带有页眉页脚的excel
    通过ajax记录打印信息
    reportConfig.xml两种数据源连接的配置方式
    润乾填报页面导入excel后增加js动作
    matplotlib多plot可视化
    Python之SGDRegressor
    Python之岭回归
    Python之随机梯度下降
  • 原文地址:https://www.cnblogs.com/shawk/p/13277399.html
Copyright © 2020-2023  润新知