LCA的离线算法 tarjan

对于最近公共祖先问题,我们先来看这样一个性质,当两个节点(u,v)的最近公共祖先是x时,那么我们可以确定的说,当进行后序遍历的时候,必然先访问完x的所有子树,然后才会返回到x所在的节点。这个性质就是我们使用Tarjan算法解决最近公共祖先问题的核心思想。

同时我们会想这个怎么能够保证是最近的公共祖先呢?我们这样看,因为我们是逐渐向上回溯的,所以我们每次访问完某个节点x的一棵子树,我们就将该子树所有节点放进该节点x所在的集合,并且我们设置这个集合所有元素的祖先是该节点x。那么我们有理由相信, 任何一个不属于已经访问的节点和已经访问的节点的LCA一定是当前这个根节点. 于是我们每次访问完一棵子树, 只需要将子树放进根节点对应的集合即可.

对于一棵子树所有节点,祖先都是该子树的根节点,所以我们在回溯的时候,时常要更新整个子树的祖先,为了方便处理,我们使用并查集维护一个集合的祖先。总的时间复杂度是O(n+q)的,因为dfs是O(n)的,然后对于询问的处理大概就是O(q)的。

以上摘自http://blog.csdn.net/geniusluzh/article/details/6609685

以下是整体代码:

#include <cstdio>
#include <cstring>

const int MAXNODE = 40010;
const int MAXEDGE = 80010;
typedef int Type;
const Type INF = 0x3f3f3f3f; //1e20

//存放边
struct Edge{
    int u, v, next;
    Type dis;
    Edge() {}
    Edge(int u, int v, int next, Type dis): u(u), v(v), next(next), dis(dis) {}
};

//存放问题,ok标记指的是这个问题被回答了没有
struct Question{
    int u, v, next;
    bool ok;
    Question() {}
    Question(int u, int v, int next): u(u), v(v), next(next), ok(false){}
};

struct Tarjan{

    Edge edges[MAXEDGE];
    Question ques[MAXNODE];
    int n, m, q;
    //用邻接表存储问题和边,LCA指的是第几个问题的最近公共祖先,f数组用来记录祖先结点
    int EdgeHead[MAXNODE], f[MAXNODE], LCA[MAXNODE], QuesHead[MAXNODE];
    bool vis[MAXNODE];
    Type dis[MAXNODE];

    void init(int n) {
        this->n = n;
        m = q = 0;
        memset(vis, 0, sizeof(vis));
        memset(EdgeHead, -1, sizeof(EdgeHead));
        memset(QuesHead, -1, sizeof(QuesHead));
        memset(LCA, 0, sizeof(LCA));
    }

    //添加的应该是双向边
    void AddEdge(int u, int v, Type dis) {
        edges[m] = Edge(u, v, EdgeHead[u], dis);
        EdgeHead[u] = m++;
    }

    //添加的应该是双向边
    void AddQues(int x, int y) {
        ques[q] = Question(x, y, QuesHead[x]);
        QuesHead[x] = q++;
    }

    int find(int x) {
        return x == f[x] ? x : f[x] = find(f[x]);
    }

    //tarjan,遍历u结点的所有结点,并将u结点的所有直系子节点的父亲设为u,再进行查询
    void dfs(int u) {
        f[u] = u;
        vis[u] = true;
        for (int i = EdgeHead[u]; ~i; i = edges[i].next) {
            int v = edges[i].v;
            if (!vis[v]) {
                dis[v] = dis[u] + edges[i].dis;
                dfs(v);
                f[v] = u;
            }
        }

        //查询,如果其中一个结点刚好是u,且另一个结点已经遍历过,则u和v的LCA就是f[v]
        for (int i = QuesHead[u]; ~i; i = ques[i].next) {
            //如果已经被询问过了
            if (ques[i].ok) continue;
            int v = ques[i].v;
            if (vis[v]) {
                LCA[i] = find(v);
                ques[i].ok = ques[i ^ 1].ok = true;
            }
        }
    }
}tarjan;

int main() {
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值