zoukankan      html  css  js  c++  java
  • 【模板】最近公共祖先 LCA

    一个月以前学的最近公共祖先。一直以为我理解的最够深刻了,直到遇见真的比较复杂的题之后,才发现自己的漏洞。

    那么今天就借助一道模板题来总结一下吧。

    下面是洛谷模板的题面。

    下面是样例及解释。

    Input

    5 5 4
    3 1
    2 4
    5 1
    1 4
    2 4
    3 2
    3 5
    1 2
    4 5
    

    Output

    4
    4
    1
    4
    4


    那么接下来就详细说说LCA是怎么回事吧。

      首先是一般的LCA。

    我是盗图小丸子,对图作者表示歉意与感谢

      那么我们就先举个栗子。

      比如4和16的LCA就是3.而对于9和10的LCA则是7.对于17和18则又是3。

      那么暴力做法就很显然了不是吗?


    暴力做法

      我们就以17和18为例。既然是要找LCA,那么我们就让他们往上去走。

      17>14>10>7>3
      18->16->12->8->5->318>16>12>8>5>3

      第一次遇到的地方就是LCA(17,18)的值了。DFS暴力实现啊?

      如果你觉得会这些就足够了的话(那您可就是神了啊),TLE欢迎你。大多数题一般是不会很快(-->TLE)的。

      不信我们就举个栗子吧(还是上图),比如4和18.显然这样暴力是不太好的。我们显然是希望4可以等18上去了之后再走。

      那么就有一种很优秀(玄学)的LCA了。


    倍增LCA

      当你看到这里,这题才刚刚开始啊。

      那么首先先说倍增吧(有专门讲倍增的文章这里就简单说一下啦)。

    !!倍增

      倍增就是按照2的n次幂来往上走。但是我们一般是从大往小跳,当用大的跳过了之后,就用小的再跳回来(好蠢的样子)。

      还是举个栗子吧(还是17和18)

      17>3
      18>5>3

      这样明显就是快了不少啊。复杂度是O(nlogn);对于大多数题来说就足够了。

    回到LCA

      所以对于倍增LCA来说,我们就需要记录一下每一个节点的幂次方爸爸是谁了啊。

      那么我们跑一遍dfs就解决了。(deep是节点的深度,fa是存某数的幂次方爸爸的)

    void dfs(int f,int fath) {
        deep[f]=deep[fath]+1;
        fa[f][0]=fath;
        for(int i=1;(1<<i)<=deep[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=edge[i].nex)
        	if(edge[i].t!=fath) 
                dfs(edge[i].t,f);
        return;
    }
    

      然后我们就可以上LCA了。

      在此之前,我喜欢先加一个常数的优化。(当然你也可以不加,直接套用log2(x)->x是次方,就应该也可以啊)

     for(int i=1;i<=n;i++) 
            lg[i]=lg[i-1]+(1<<lg[i-1]==i);//看不懂就自己手推好啦,这可救不了你
    

      然后就是LCA啦,我们想把他们都调到一个高度再找,这样就可以实现了。

    int LCA(int x,int y) {
        if(deep[x]<deep[y]) 
            swap(x,y);
        while(deep[x]>deep[y]) 
            x=fa[x][lg[deep[x]-deep[y]]-1];
        if(x==y) 
            return x;
        for(int k=lg[deep[x]]-1;k>=0;k--)
        	if(fa[x][k]!=fa[y][k]) {
              	x=fa[x][k];
                y=fa[y][k];
            }
        return fa[x][0];
    }
    

      很好,我自以为讲的还不错,勉强看吧(毕竟语文不好)。下面放完整版。

    Code(代码风格2.1版)

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    using namespace std;
    
    const int MA=5e5+1;
    struct ss{
        int t,nex;
    }edge[2*MA];
    int n,m,s,tot;
    int fa[MA][22];
    int lg[MA],head[MA],deep[MA];
    
    int read() {
        int x=0;
        bool flag=0;
        char ch=getchar();
        if(ch=='-') 
            flag=1;
        while(ch<'0'||ch>'9') 
            ch=getchar();
        while(ch>='0'&&ch<='9') {
        	x*=10;
            x+=ch-'0';
            ch=getchar();
        }
        if(flag) return -x;
        return x;
    }//快读压行什么的,我才不要 傲娇】
    
    void add(int x,int y) {
        edge[++tot].t=y; 
        edge[tot].nex=head[x];
        head[x]=tot;
    }
    
    void dfs(int f,int fath) {
        deep[f]=deep[fath]+1;
        fa[f][0]=fath;
        for(int i=1;(1<<i)<=deep[f];i++) 
            fa[f][i]=fa[fa[f][i-1]][i-1];
        for(int i=head[f];i;i=edge[i].nex)
        	if(edge[i].t!=fath) 
                dfs(edge[i].t,f);
        return;
    }
    
    int LCA(int x,int y) {
        if(deep[x]<deep[y]) 
            swap(x,y);
        while(deep[x]>deep[y]) 
            x=fa[x][lg[deep[x]-deep[y]]-1];
        if(x==y) 
            return x;
        for(int k=lg[deep[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 main()
    {
        n=read();
        m=read();
        s=read();
        for(int i=1;i<n;i++) {
            int x=read();
            int y=read();
            add(x,y); 
            add(y,x);
        }
        for(int i=1;i<=n;i++) 
            lg[i]=lg[i-1]+(1<<lg[i-1]==i);
        dfs(s,0);
        for(int i=1;i<=m;i++) {
            int a=read();
            int b=read();
            int ans=LCA(a,b); 
            printf("%d
    ",ans);
        }
        return 0;
    }

    然后我这题还能用 树链剖分,还有约束RMQ求LCA,以及tarjan求LCA(这些我全都不会)。之后会了的话会回来不上的,有兴趣的可以之后在自学一下啦。

    那么就这样啦。谢谢

    ---OI是信仰,是真正应该被认真以待的东西.....!
  • 相关阅读:
    ABAP 程序中的类 沧海
    ABAP类的方法(转载) 沧海
    More than 100 ABAP Interview Faq's(2) 沧海
    SAP and ABAP Memory总结 沧海
    ABAP Frequently Asked Question 沧海
    ABAP System Reports(Additional functions) 沧海
    ABAP Questions Commonly Asked 1 沧海
    ABAP Tips and Tricks 沧海
    ABAP System Fields 沧海
    ABAP 面试问题及答案(一):数据库更新及更改 SAP Standard (转) 沧海
  • 原文地址:https://www.cnblogs.com/qxyzili--24/p/10456050.html
Copyright © 2011-2022 走看看