码迷,mamicode.com
首页 > 其他好文 > 详细

LCA

时间:2018-10-08 21:39:03      阅读:169      评论:0      收藏:0      [点我收藏+]

标签:add   void   for   family   read   标记   tarjan   而不是   访问   

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\n",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\n",max(ans[2*i-1],ans[2*i]));
    return 0;
}

 

LCA

标签:add   void   for   family   read   标记   tarjan   而不是   访问   

原文地址:https://www.cnblogs.com/Dxy0310/p/9757129.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!