LCA离线算法学习笔记

算法思想:

LCA离线,采用的是递归的tarjan算法,利用了树深度优先遍历的性质可以在一次遍历过程中巧妙的求解出查询的最接近公共祖先,时间复杂度是O(n+q),但前提是需要离线保存所有询问。
算法思路如下:
当前dfs遍历到节点u,先将vis[u]标记成true,然后处理以u为根节点的子树,子树处理完成后,将这棵子树中的所有点的祖先都设成u,这时候处理跟u相关的所有询问(u,v),如果vis[v]==true,那么lca(u,v)就等于v当前设置的祖先。
举个例子,示意图如下:
这里写图片描述
对于正确性,这里对于询问v考虑三种情况:

  1. v属于u的子树,这时v已经被访问过,所以vis[v]==true。由于之前处理的了u的子节点的祖先都为u,所以v当前设置的祖先也是u,故lca(u,v)=u,很显然是正确的。
  2. v属于u的祖先,既然是dfs过程,在访问到u之前,u的祖先一定已经访问过,所以vis[v]==true。因为当前处理的是v的子树u,所以以v为根的子树还没有被处理完全,故这时候v的祖先还是它自己,所以lca(u,v)=v,正确。

  3. v属于u的兄弟节点或者u祖先的兄弟节点为根的子树中,这样v有可能被访问过,也有可能没有。没有访问过自然不需要处理,如果v被访问了,那么v当前设置的祖先是什么呢?以上图作为例子,假设u是5,v是3,在访问到u的时候,v已经被访问了,那么根据算法的步骤,此时v的祖先被设置成节点1,从图中观察,lca(u,v)确实就是节点1,故算法正确。

其实tarjan算法就是利用了树的dfs的性质,从一个节点v遍历到另一个节点u,肯定会经过lca(u,v),此时按照算法将v的祖先就设置成lca(u,v),就可以保证算法正确。至于寻找祖先的操作,很显然就利用并查集来优化查询速度。


例题:

还是用LCA入门题:HDU-2586
题目链接:http://acm.split.hdu.edu.cn/showproblem.php?pid=2586

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 44444;

struct Node {
    int v, w;
};

struct QNode {
    int v, id;
};

struct Query {
    int u, v, lca;
};

struct LCA {
    int n, m;
    vector <Node> tree[MAXN];
    vector <QNode> qtree[MAXN];
    Query q[MAXN];
    int dist[MAXN], pa[MAXN];
    bool vis[MAXN];

    void init(int n, int m) {
        this->n = n;
        this->m = m;
        memset(vis, false, sizeof(vis));
        memset(dist, 0, sizeof(dist));
        for (int i = 1; i <= n; i++) pa[i] = i;
        for (int i = 1; i <= n; i++) tree[i].clear();
        for (int i = 1; i <= m; i++) qtree[i].clear();
    }

    void addEdge(int u, int v, int w) {
        tree[u].push_back((Node) {v, w});
        tree[v].push_back((Node) {u, w});
    }

    void addQuEdge(int u, int v, int id) {
        q[id] = (Query){u, v, -1};
        qtree[u].push_back((QNode) {v, id});
        qtree[v].push_back((QNode) {u, id});
    }

    int Find(int x) {
        return pa[x] == x ? x : pa[x] = Find(pa[x]);
    }

    void tarjan(int u) {
        vis[u] = true;
        for (int i = 0; i < (int)tree[u].size(); i++) {
            int v = tree[u][i].v, w = tree[u][i].w;
            if (vis[v]) continue;
            dist[v] = dist[u] + w;
            tarjan(v);
            pa[v] = u;
        }
        for (int i = 0; i < (int)qtree[u].size(); i++) {
            int v = qtree[u][i].v, id = qtree[u][i].id;
            if (vis[v]) {
                q[id].lca = Find(v);
            }
        }
    }

} Lca;

int main() {
    //freopen("in.txt", "r", stdin);
    int T;
    scanf("%d", &T);
    while (T--) {
        int n, m;
        scanf("%d%d", &n, &m);
        Lca.init(n, m);
        for (int i = 1; i < n; i++) {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            Lca.addEdge(u, v, w);
        }
        for (int i = 1; i <= m; i++) {
            int u, v;
            scanf("%d%d", &u, &v);
            Lca.addQuEdge(u, v, i);
        }
        Lca.tarjan(1);
        for (int i = 1; i <= m; i++) {
            int u = Lca.q[i].u, v = Lca.q[i].v, lca = Lca.q[i].lca;
            printf("%d\n", Lca.dist[u] + Lca.dist[v] - 2 * Lca.dist[lca]);
        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值