• 倍增求LCA


    P3379 【模板】最近公共祖先(LCA)

    【题目大意】

    给你树的节点个数(n),询问个数(m)和树根(s),
    输入(n,m,s);
    输入(n)(x,y.)表示(x,y)结点之间有一条边
    输入(m)(a,b.)表示求(a,b)的最近公共祖先

    【思路】

    下面的解释均以这个图为例

    今天用这道题来说一下倍增,先预处理出每个节点的深度,和lg数组,就是(log_2{i})的值.所谓倍增,就是按(2)的倍数来增大,也就是跳(1,2,4,8,16,32……)不过在这我们不是按从小到大跳,而是从大向小跳,即按(……32,16,8,4,2,11)来跳,如果大的跳不过去,再把它调小。这是因为从小开始跳,可能会出现“悔棋”的现象。拿(5)为例,从小向大跳,(5≠1+2+4),所以我们还要回溯一步,然后才能得出(5=1+4);而从大向小跳,直接可以得出(5=4+1)。这也可以拿二进制为例,(5(101)),从高位向低位填很简单,如果填了这位之后比原数大了,那我就不填,这个过程是很好操作的。
    还是以(17)(18)为例(此例只演示倍增,并不是倍增LCA算法的真正路径)
    (17->3)
    (18->5->3)
    可以看出向上跳的次数大大减小。这个算法的时间复杂度为(O(nlogn)),已经可以满足大部分的需求。
    想要实现这个算法,首先我们要记录各个点的深度和他们(2^{i})级的的祖先,用数组(depth)表示每个节点的深度,(fa[i][j])表示节点(i)(2^j)级祖先。 代码如下:

    void dfs(int f,int fath)//f表示当前节点,fath表示他的父亲
    {
        depth[f]=depth[fath]+1;
        fa[f][0]=fath;
        for(int i=1;(1<<i)<=depth[f];i++)
          fa[f][i]=fa[fa[f][i-1]][i-1];
    //这个转移是核心.意思是f的2^i祖先等于f的2^(i-1)祖先的2^(i-1)祖先
    //2^i=2^(i-1)*2^(i-1) 
        for(int i=head[f];i;i=e[i].nex)
          if(e[i].t!=fath)
            dfs(e[i].t,f);
    }
    

    预处理完毕后,我们就可以去找它的(LCA)了,为了让它跑得快一些,我们可以加一个常数优化

    for(int i=1;i<=n;i++) //预先算出log_2(i)+1的值,用的时候直接调用就可以了
      lg[i]=lg[i-1]+(1<<lg[i-1]==i);  //看不懂的可以手推一下
    

    接下来就是倍增(LCA)了,我们先把两个点提到同一高度,再统一开始跳。
    但我们在跳的时候不能直接跳到它们的(LCA),因为这可能会误判,比如(4)(8),在跳的时候,我们可能会认为(1)是它们的(LCA),但(1)只是它们的祖先,它们的(LCA)其实是(3)。所以我们要跳到它们(LCA)的下面一层,比如(4)(8),我们就跳到(4)(5),然后输出它们的父节点,这样就不会误判了。

    int lca(int x,int y)
    {
        if(depth[x]<depth[y])//保证x比y深.便于一会调到同一深度
          swap(x,y);
        while(depth[x]>depth[y])//调到同一深度.
          x=fa[x][lg[depth[x]-depth[y]]-1];
        if(x==y)//如果到了同一个点了,那就到了lca了.
          return x;
        for(int k=lg[depth[x]]-1;k>=0;k--)//不断往上调.
          if(fa[x][k]!=fa[y][k])//如果两个父亲不一样. 
            x=fa[x][k],y=fa[y][k];//继续往上跳
        return fa[x][0];//返回
    }
    

    完整的求(17)(18)的LCA的路径:
    (17->10->7->3)
    (18->16->8->5->3)
    解释:首先,(18)要跳到和(17)深度相同,然后(18)(17)一起向上跳,一直跳到(LCA)的下一层(1(7)(7)(18)(5)),此时(LCA)就是它们的父亲
    这个题就是这样.(so) (easy).
    完整代码

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    #include<cstring>
    #include<string>
    #include<algorithm>
    #include<iomanip>
    #include<cstdlib>
    #include<queue>
    #include<map>
    #include<set>
    #include<stack>
    #include<vector>
    #define ll long long
    using namespace std;
    inline int read()
    {
       int s=0,w=1;
       char ch=getchar();
       while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
       while(isdigit(ch)) s=s*10+ch-'0',ch=getchar();
       return s*w;
    }
    struct yyy
    {
        int t,nex;
    }e[2*500001];
    int depth[500001],fa[500001][22],lg[500001],head[500001];
    int tot;
    void add(int x,int y)
    {
        e[++tot].t=y; 
        e[tot].nex=head[x];
        head[x]=tot;
    }
    void dfs(int f,int fath)
    {
        depth[f]=depth[fath]+1;
        fa[f][0]=fath;
        for(int i=1;(1<<i)<=depth[f];i++)
          fa[f][i]=fa[fa[f][i-1]][i-1];
        for(int i=head[f];i;i=e[i].nex)
          if(e[i].t!=fath)
            dfs(e[i].t,f);
    }
    int lca(int x,int y)
    {
        if(depth[x]<depth[y])
          swap(x,y);
        while(depth[x]>depth[y])
          x=fa[x][lg[depth[x]-depth[y]]-1];
        if(x==y)
          return x;
        for(int k=lg[depth[x]]-1;k>=0;k--)
          if(fa[x][k]!=fa[y][k])
            x=fa[x][k],y=fa[y][k];
        return fa[x][0];
    }
    int n,m,s;
    int main()
    {
        n=read(),m=read(),s=read();
        for(int i=1,x,y;i<=n-1;i++)
        	x=read(),y=read(),add(x,y),add(y,x);
        dfs(s,0);
        for(int i=1;i<=n;i++)
          lg[i]=lg[i-1]+(1<<lg[i-1]==i);
        for(int i=1,x,y;i<=m;i++)
            x=read(),y=read(),printf("%d
    ",lca(x,y));
        return 0;
    }
    

    倍增也就是这个思想了吧.

  • 相关阅读:
    common-fileupload上传图片并显示图片
    common-fileupload上传文件
    Hibernate映射文件配置(hbm.xml和注解方式)
    hdu1272:小希的迷宫(并查集)
    hdu1272:小希的迷宫(并查集)
    hdu1102:Constructing Roads(最小生成树)
    hdu1102:Constructing Roads(最小生成树)
    hdu1054:Strategic Game(最小点覆盖)
    hdu1054:Strategic Game(最小点覆盖)
    hdu2255:奔小康赚大钱(KM)
  • 原文地址:https://www.cnblogs.com/Xchu/p/11715593.html
Copyright © 2020-2023  润新知