zoukankan      html  css  js  c++  java
  • 倍增求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;
    }
    

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

  • 相关阅读:
    1012 The Best Rank (25 分)(排序)
    1011. World Cup Betting (20)(查找元素)
    1009 Product of Polynomials (25 分)(模拟)
    1008 Elevator (20 分)(数学问题)
    1006 Sign In and Sign Out (25 分)(查找元素)
    1005 Spell It Right (20 分)(字符串处理)
    Kafka Connect 出现ERROR Failed to flush WorkerSourceTask{id=local-file-source-0}, timed out while wait
    flume、kafka、avro组成的消息系统
    Java23种设计模式总结【转载】
    Java编程 思维导图
  • 原文地址:https://www.cnblogs.com/Xchu/p/11715593.html
Copyright © 2011-2022 走看看