zoukankan      html  css  js  c++  java
  • LCA

    LCA的定义:

        在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,
        而最近公共祖先,就是两个节点在这棵树上深度最大的公共的祖先节点,
        其实就是是两个点在这棵树上距离最近的公共祖先节点

    用途:

          主要用来处理两个点有且只有一条确定的最短路径时的路径。

    如何求解LCA:

      1.倍增:

        所谓倍增就是成倍的增长,只不过是以2为底数而已,在算法中就是成倍地跳,而不是一个一个地跳
        实现:先利用dfs预处理出每个节点相对于根结点的深度length[u],
           用f[u][i]表示节点u向上跳2^i所能到达的深度的节点,
           依次预处理出每个节点的length和f,
           再边询问边求两点的LCA,后回答。
           若是询问的节点x,y不在同一个深度,则将深度更深的节点往上蹦,
           直到将两者保持在同一深度。
           若此时x,y在同一深度且有x==y则说明此时的x或y就是初始x,y的LCA,
           直接返回就行了。
           将x,y同时,同幅度地向上蹦(能够执行这一步说明并不是前一种x或y为LCA情况),
           直到两者有蹦后到达的点的祖先相同,此时返回该结点的祖先即可,否则一直向上蹦。

      注意:为了节约时间,更快的找到LCA,在向上跳的过程中贪心地先迈大步子后迈小步子,不过要在深度允许的范围内蹦;
         f[u][i]=f[f[u][i-1]][i-1]含义为u结点向上跳2^(i-1)后到达的节点再向上跳2^(i-1)所到达的最终节点,因为2^(i-1)+2^(i-1)=2*(2^(i-1))=2^i。
         在求LCA的题目中通常会给不同的边权,此时只要在dfs的同时用一个数组dis[u]记录到u点的权值和即可.

    #include<bits/stdc++.h>
    using namespace std;
    #define maxn 500050
    int head[maxn],length[maxn];
    int f[maxn][100];
    int n,m,k,ans,cnt,s;
    struct edge
    {
        int to,nxt;
    }p[maxn*2];
    void add(int x,int y)
    {
        ++cnt;
        p[cnt].to=y;
        p[cnt].nxt=head[x];
        head[x]=cnt;
    }
    void dfs(int u,int fa)
    {
        length[u]=length[fa]+1;
        f[u][0]=fa;
        for(int i=1;(1<<i)<=length[u];i++)
            f[u][i]=f[f[u][i-1]][i-1];
        for(int i=head[u];i;i=p[i].nxt)
        {
            int v=p[i].to;
            if(v==fa)
                continue;
            dfs(v,u);   
        } 
     } 
    
    int lca(int x,int y)
    {
        if(length[x]>length[y])
            swap(x,y);
        for(int i=20;i>=0;i--)
        {
            if(length[x]<=length[y]-(1<<i))
                y=f[y][i];
        }
        if(x==y)
            return x;
        for(int i=20;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%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);
         } 
         dfs(s,0);
         for(int i=1;i<=m;i++)
         {
            int a,b;
            scanf("%d%d",&a,&b);
            printf("%d
    ",lca(a,b));
         }
         return 0;
    } 

      2.Tarjan

        完美地利用深搜的性质以达到优化时间复杂度的效果;
        1.任选一个节点为根结点,从根结点开始遍历(dfs)
        2.遍历当前节点u的所有子节点v,并标记v已被访问
        3.若u仍有子节点,则继续做2中步骤,知道做完(叶结点)
        4.合并v到u上(利用并查集)
        5.寻找与u节点有关的所有询问(u,v),若v已做完(被标记过),
        则此时的LCA为v被合并的父亲节点,
        因为v已做完,表示找到u的时候是从u的父结点转过来的,就是一个分叉口

    Tarjan(u)//marge和find为并查集合并函数和查找函数
    {
        for each(u,v)    //访问所有u子节点v
        {
            Tarjan(v);        //继续往下遍历
            marge(u,v);    //合并v到u上
            标记v被访问过;
        }
        for each(u,e)    //访问所有和u有询问关系的e
        {
            如果e被访问过;
            u,e的最近公共祖先为find(e);
        }
    }
    #include<bits/stdc++.h>
    using namespace std;
    #define maxn 5000050
    inline int read()
    {
        int x=0,f=1;
        char ch=getchar();
        while(ch<'0'||ch>'9')
        {
            if(ch=='-')
                f=-1;
            ch=getchar();
        }
        while(ch>='0'&&ch<='9')
        {
            x=(x<<1)+(x<<3)+(ch-'0');
            ch=getchar();
        }
        return x*f;  
    }
    int cnt,cntt,n,m,s;
    int head[maxn],fa[maxn],headd[maxn],ans[maxn],pre[maxn];
    bool vis[maxn];
    struct edge
    {
        int to,nxt;
    }p[maxn*2],q[maxn*2];
    void addedge(int x,int y)
    {
        ++cnt;
        p[cnt].nxt=head[x];
        p[cnt].to=y;
        head[x]=cnt;
    }
    void addquery(int x,int y)
    {
        ++cntt;
        q[cntt].to=y;
        q[cntt].nxt=headd[x];
        headd[x]=cntt;
    }
    
    int find(int x)
    {
        return fa[x]==x ?  fa[x] : fa[x]=find(fa[x]);
    }
    
    void unionn(int a,int b)
    {
        int x=find(a);
        int y=find(b);
        if(x!=y)
            fa[y]=x;
    }
    
    void Tarjan(int x)
    {
        for(int i=head[x];i;i=p[i].nxt)
        {
            int v=p[i].to;
            if(v!=pre[x])
            {
                pre[v]=x;
                Tarjan(v);
                unionn(x,v);
                vis[v]=1;
            }
            //cout<<"yes"<<endl;
        }
        for(int i=headd[x];i;i=q[i].nxt)
        {
            int v=q[i].to;
            if(vis[v])
            {
    
                ans[i]=find(v);
            //  cout<<find(v)<<endl;
            }
        }
    }
    int main()
    {
        scanf("%d%d%d",&n,&m,&s);
        for(int i=1;i<n;i++)
        {
            int a,b;
            a=read();
            b=read();
        //  scanf("%d%d",&a,&b);
            addedge(a,b);
            addedge(b,a);
        }
        for(int i=1;i<=m;i++)
        {
            int a,b;
            a=read();
            b=read();
            //scanf("%d%d",&a,&b);
            addquery(a,b);
            addquery(b,a);
        }
        for(int i=1;i<=n;i++)
        {
            fa[i]=i;
            pre[i]=i;
        }
    
        Tarjan(s);
    //  cout<<"yes"<<endl;
        for(int i=1;i<=m;i++)
            printf("%d
    ",max(ans[2*i-1],ans[2*i]));
        return 0;
    }
  • 相关阅读:
    053518
    Ubuntu 20.04, 19.10 or 19.04出现libqtgui4 : Depends: libpng120 (>= 1.2.134) but it is not installed
    Ubuntu下安装最新OpenJdk1.8
    c#_FFMPEG使用心得(推流与拉流)
    [WPF 自定义控件]简单的表单布局控件
    WPF调用图片路径,或资源图片
    WPF中的数据模板(DataTemplate)
    MahApps.Metro 官方文档
    MahApps.Metro 图标
    WPF简单导航框架(Window与Page互相调用)
  • 原文地址:https://www.cnblogs.com/Dxy0310/p/9757129.html
Copyright © 2011-2022 走看看