zoukankan      html  css  js  c++  java
  • LCA——最近公共祖先

    今天终于把倍增的LCA搞懂了!尽管周测都没写,尽管lca其实很简单,但这也是进度君的往前一点点的快乐。学渣的呻吟。

    倍增的lca其实关键就在于二进制的二进制的拆分(显然是两次的拆分,很奇妙,懂二进制的自然不觉得什么)。把最关键的地方在这里列举一下吧:

    1.f[fa][i]=f[f[fa][i-1]][i-1];类似于状态转移,i表示2^i可以表示fa的2的i次方的祖先所以当前的fa的2^i的祖先就是它2-1次方的祖先的2-1次方的祖先

    想要理解为什么是这样,1+1=2;2+2=4;4+4=8;8+8=16;故2^i-1+2^i-1=2^i;到这里就可以理解了吧。

    2.for(int i=t;i>=0;i--) 这里的t是t=(int)(log(n)/log(2))+1;的出来的n是最大的数目也就是log数,if(depth[f[y][i]]>=depth[x]) y=f[y][i];这里的是指假如当前的深度和x的深度比,能调到和x一样深就跳,这个代码完成了将y跳到x,二进制的拆分就是如此神奇!!!。(dalao勿喷蒟蒻没见过二进制拆分)

    3.for(int i=t;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];这里的也就是x,y虽然到了同样的深度但是呢,公共祖先还是没有出来所以可以进行同时向上爬(我要一步一步向上爬)因为二进制的拆分所以最后都会达到距离最近公共祖先最近的子节点处,所以最后输出x的父亲节点就是x,y的公共子节点了,二进制的拆分就是如此神奇!!!。(dalao勿喷蒟蒻没见过二进制拆分)

    这就是lca的具体之处认真思考其实都很简单(无奈作业太多)多思考啊。

    复杂度为 O((n+m)logn)原因预处理处。

    代码:

    #include<iostream>
    #include<iomanip>
    #include<cstdio>
    #include<ctime>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<map>
    using namespace std;
    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*10+ch-'0';ch=getchar();}
        return x*f;
    }
    const int maxn=500001<<1;
    const int maxx=500001;
    int n,m,s,t;
    int lin[maxn],nex[maxn],ver[maxn],len=0;
    int depth[maxx],f[maxx][30];
    void add(int x,int y)
    {
        ver[++len]=y;
        nex[len]=lin[x];
        lin[x]=len;
    }
    void dfs(int fa,int fath)
    {
        depth[fa]=depth[fath]+1;
        f[fa][0]=fath;
        for(int i=1;i<=t;i++)
            f[fa][i]=f[f[fa][i-1]][i-1];
        for(int i=lin[fa];i;i=nex[i])
            if(ver[i]!=fath)
                dfs(ver[i],fa);
    }
    int lca(int x,int y)
    {
        if(depth[x]>depth[y])
            swap(x,y);
        for(int i=t;i>=0;i--)
            if(depth[f[y][i]]>=depth[x])
                y=f[y][i];
        if(x==y)
            return x;
        for(int i=t;i>=0;i--)
            if(f[x][i]!=f[y][i])
                x=f[x][i],y=f[y][i];
        return f[x][0];
    }
    int main()
    {
        //freopen("1.in","r",stdin);
        n=read();m=read();s=read();
        t=(int)(log(n)/log(2))+1;
        for(int i=1;i<n;i++)
        {
            int x,y;
            x=read();y=read();
            add(x,y);add(y,x);
            
        }
        dfs(s,0);
        for(int i=1;i<=m;i++)
        {
            int x,y;
            x=read();y=read();
            printf("%d
    ",lca(x,y));
        }
        return 0;
    }
    View Code

    学长说求lca 方法有:倍增(如上),tanjan(如下),st(不懂),树剖(不会)。

    那就浅谈tanjan离线来求lca吧。主要运用并查集的回溯的神奇效应,很好懂得。

    当前当前节点回溯时查询问题点是否回溯过,一旦回溯过就有父亲了。没有的话让另一个对应点来更新具体细节看代码。

    #include<bits/stdc++.h>
    #include<iostream>
    #include<iomanip>
    #include<cstdio>
    #include<cstring>
    #include<string>
    #include<cmath>
    #include<ctime>
    #include<algorithm>
    #include<queue>
    #include<deque>
    #include<vector>
    #include<map>
    #include<stack>
    #include<set>
    #include<bitset>
    using namespace std;
    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*10+ch-'0';ch=getchar();}
        return x*f;
    }
    inline void put(int x)
    {
        if(x==0){putchar('0');putchar('
    ');return;}
        if(x<0)putchar('
    '),x=-x;
        int num=0;char ch[50];
        while(x)ch[++num]=x%10+'0',x/=10;
        while(num)putchar(ch[num--]);
        putchar('
    ');return;
    }
    const int maxn=500002;
    vector<int>q[maxn],q1[maxn];
    int n,m,s;
    int f[maxn],vis[maxn],ans[maxn];//存答案
    int ver[maxn<<1],nex[maxn<<1],lin[maxn<<1],len=0;
    void add(int x,int y)
    {
        ver[++len]=y;
        nex[len]=lin[x];
        lin[x]=len;
    }
    int getfather(int x){return f[x]==x?x:f[x]=getfather(f[x]);}
    void tarjan(int x)
    {
        vis[x]=1;
        for(int i=lin[x];i;i=nex[i])
        {
            int tn=ver[i];
            if(vis[tn]!=0)continue;//防止便利到父亲啊
            //等价于 if(vis[tn]==1)continue;因为儿子没回溯父亲也不可能回溯
            tarjan(tn);
            f[tn]=x;
            vis[tn]=2;//直接标记回溯过了
        }
        for(int i=0;i<q[x].size();i++)
        {
            int tn=q[x][i];
            if(vis[tn]==2)//如果对应点回溯过那么父亲就有了!
            {
                ans[q1[x][i]]=getfather(tn);
            }
        }
        //vis[x]=2;这里标记回溯当然也是可以的啦
        //因为对x是否回溯对当前x节点的对应点询问和x是否回溯过无关所以可以进行上述面的标记
        
    }
    int main()
    {
        //freopen("1.in","r",stdin);
        n=read();m=read();s=read();
        for(int i=1;i<n;i++)
        {
            int x,y;
            x=read();y=read();
            add(x,y);add(y,x);
        }
        for(int i=1;i<=n;i++)f[i]=i;
        for(int i=1;i<=m;i++)
        {
            int x,y;
            x=read();y=read();
            q[x].push_back(y);q[y].push_back(x);
            q1[x].push_back(i);q1[y].push_back(i);//为了O(n)输出答案而准备
        }
        tarjan(s);
        for(int i=1;i<=m;i++)put(ans[i]);
        return 0;
    }

    复杂度 O(n+2*m)-->O(n+m);

    高歌取醉欲自慰,起舞落日争光辉。

  • 相关阅读:
    Best Time to Buy and Sell Stock III
    Valid Palindrome
    Longest Substring Without Repeating Characters
    Copy List with Random Pointer
    Add Two Numbers
    Recover Binary Search Tree
    Anagrams
    ZigZag Conversion
    Merge k Sorted Lists
    Distinct Subsequences
  • 原文地址:https://www.cnblogs.com/chdy/p/9688552.html
Copyright © 2011-2022 走看看