zoukankan      html  css  js  c++  java
  • 最近公共祖先(LCA)问题

    最近公共祖先

    定义:给定一颗有根树,若结点 z 既是 x 的祖先,也是 y 的祖先,则称 z 是 x,y 的公共祖先。在 x,y 所有的公共祖先中,深度最大的一个称为 x,y 的最近公共祖先,简称(LCA(x,y))

    求解最近公共祖先一般有三种解法:向上标记法,树上倍增法和 Tarjan 算法。

    1.向上标记法

    即对于任何两个结点 x , y ,分别从x y 向上走并标记它们所有经过的节点,第一次相遇的节点即为最近公共祖先。其时间复杂度最坏为(O(n^2))

    2.树上倍增法

    树上倍增法在很多问题都有很广泛的应用,当然最近公共祖先也可以用树上倍增算法来求,其基本思想是:

    设d[x]>=d[y],其中d[x]表示x的深度,然后利用二进制思想,将x不断调整直至和y在同一深度,检查此时x和y是否相等,若相等即为最近公共祖先,否则将x,y一起向上调整直至相等,此时则为最近公共祖先。

    该算法时间复杂度为:(O((n+m)logn))具体代码(裸题,求权值)为:

    #include<bits/stdc++.h>
    
    using namespace std;
    const int maxn=5e5+10;
    int head[maxn],dis[maxn],d[maxn],f[maxn][20];
    struct Edge
    {
        int nex,to,val;
    }edge[maxn<<1];
    int T,t,n,m,tot;
    queue<int> q;
    void init()
    {
        tot=0;
        memset(head,-1,sizeof(head));
        memset(d,0,sizeof(d));
    }
    void add(int from,int to,int val)
    {
        edge[++tot].to=to;
        edge[tot].val=val;
        edge[tot].nex=head[from];
        head[from]=tot;
    }
    void bfs()
    {
        q.push(1);
        d[1]=1;
        while(!q.empty())
        {
            int x=q.front();
            q.pop();
            for(int i=head[x];i!=-1;i=edge[i].nex)
            {
                int y=edge[i].to;
                if(d[y])    continue;
                d[y]=d[x]+1;
                dis[y]=dis[x]+edge[i].val;
                f[y][0]=x;
                for(int j=1;j<=t;++j)
                    f[y][j]=f[f[y][j-1]][j-1];
                q.push(y);
            }
        }
    }
    int lca(int x,int y)
    {
        if(d[x]<d[y])   swap(x,y);
        for(int i=t;i>=0;--i)
            if(d[f[x][i]]>=d[y])    x=f[x][i];
        if(x==y)   return x;
        for(int i=t;i>=0;--i){
            if(f[x][i]!=f[y][i]){
                x=f[x][i];
                y=f[y][i];
            }
        }
        return f[x][0];    
    }
    
    int main()
    {
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d %d",&n,&m);
            t=(int)(log(n)/log(2))+1;
            init();
            for(int i=1;i<n;++i){
                int a,b,val;
                scanf("%d %d %d",&a,&b,&val);
                add(a,b,val);
                add(b,a,val);
            }
            bfs();
            for(int i=1;i<=m;++i){
                int x,y;
                scanf("%d %d",&x,&y);
                printf("%d
    ",dis[x]+dis[y]-2*dis[lca(x,y)]);
            }
        }
    }
    

    3.Tarjan算法

    Tarjan算法本质上是用并查集对“向上标记法”的优化。它是一个离线算法(即将所有输入都输入完成后一起输出);其时间复杂度为:(O(n+m))

    该算法将树中所有结点分成了三类:访问并回溯过,访问但为回溯过,未访问三类。该三类点分别标记为2,1和0。该算法的思想是当一个结点被标记为2时,则将它所在集合合并到它的父节点所在的集合中,这就相当于每个完成回溯的结点都有一个指针指向其父节点,所以只需查询y所在集合的代表元素,就等价与从y一直向上走到一个开始递归但尚未回溯的结点,即(LCA(x,y))

    还是上面的题面,看代码:

    #include<bits/stdc++.h>
    
    using namespace std;
    const int maxn=5e4+10;
    const int maxe=210;
    const int inf=0x3f3f3f3f;
    struct Edge
    {
        int nex,to,val;
    }edge[maxn<<1];
    int T,n,m,t,tot;
    int head[maxn],fa[maxn],v[maxn],dis[maxn],ans[maxe];
    vector<int> query[maxn],query_id[maxn];
    void init()
    {
        tot=0;
        memset(head,-1,sizeof(head));
        memset(dis,0,sizeof(dis));
        memset(v,0,sizeof(v));
        for(int i=1;i<=n;++i){   
            fa[i]=i;
            query[i].clear();
            query_id[i].clear();
        }
    }
    void add(int from,int to,int val)
    {
        edge[++tot].to=to;
        edge[tot].val=val;
        edge[tot].nex=head[from];
        head[from]=tot;
    }
    void add_query(int x,int y,int id)
    {
        query[x].push_back(y);
        query_id[x].push_back(id);
        query[y].push_back(x);
        query_id[y].push_back(id);
    }
    int ffind(int x)
    {
        return x==fa[x]?x:fa[x]=ffind(fa[x]);
    }
    void tarjan(int x)
    {
        v[x]=1;
        for(int i=head[x];i!=-1;i=edge[i].nex)
        {
            int y=edge[i].to;
            if(v[y])  continue;
            dis[y]=dis[x]+edge[i].val;
            tarjan(y);
            fa[y]=x;
        }
        for(int i=0;i<query[x].size();++i)
        {
            int y=query[x][i],id=query_id[x][i];
            if(v[y]==2){
                int lca=ffind(y);
                ans[id]=min(ans[id],dis[x]+dis[y]-2*dis[lca]);
            }
        }
        v[x]=2;
    }
    
    int main()
    {
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d %d",&n,&m);
            init();
            for(int i=1;i<n;++i){
                int a,b,val;
                scanf("%d %d %d",&a,&b,&val);
                add(a,b,val);
                add(b,a,val);
            }
            for(int i=1;i<=m;++i){
                int x,y;
                scanf("%d %d",&x,&y);
                if(x==y)    ans[i]=0;
                else{
                    add_query(x,y,i);
                    ans[i]=inf;
                }
            }
            tarjan(1);
            for(int i=1;i<=m;++i)
                printf("%d
    ",ans[i]);
        }
        system("pause");
    }
    
  • 相关阅读:
    python基础-6 字典相关练习题
    python基础-5
    python基础-4
    python基础-3
    读书笔记:深入理解ES6 (七)
    读书笔记:深入理解ES6 (六)
    读书笔记:深入理解ES6 (五)
    读书笔记:深入理解ES6 (四)
    读书笔记:深入理解ES6 (三)
    读书笔记:深入理解ES6 (二)
  • 原文地址:https://www.cnblogs.com/StungYep/p/12252264.html
Copyright © 2011-2022 走看看