• Luogu p3379(LCA)


    传送门

    题意:

    求一颗nn个节点的树的LCALCA

    题目分析:

    复习+学习一下三种不同LCALCA的求法(特别是根据欧拉序+STST表求LCALCA)的方法。

    下面简单总结(借鉴)一下LCALCA的三种求法

    代码:

    • 树上倍增算法(在线),预处理时间复杂度O(nlogn)mathcal{O}(nlogn),每次询问的时间复杂度为O(logn)mathcal{O}(logn)
      该算法的核心为构建出一个倍增数组ance(i,j)ance(i,j),表示第ii个结点的第2j2^{j}个祖先。同时存在一个递推式:ance(i,j)=ance( ance(i,j1),j1 )ance(i,j)=ance( ~ance(i,j-1),j-1~ )
      因此我们可以先预处理出来所有的ance(i,j)ance(i,j)数组,最后对于每个询问,都根据ance(i,j)ance(i,j)进行跳转,就可以用O(logn)mathcal{O}(logn)的时间复杂度求出两个结点u,vu,vLCALCA
    #include <bits/stdc++.h>
    #define maxn 1000005
    using namespace std;
    const int LOG=20;
    struct Node{
        int to,next;
    }q[maxn];
    int head[maxn],cnt=0;
    void add_edge(int from,int to){
        q[cnt].to=to;
        q[cnt].next=head[from];
        head[from]=cnt++;
    }
    struct LCA_ST{
        int anc[maxn][LOG],depth[maxn];
        void dfs(int x,int fa,int dis){
            anc[x][0]=fa;depth[x]=dis;
            for(int i=head[x];i!=-1;i=q[i].next){
                int to=q[i].to;
                if(to==fa) continue;
                dfs(to,x,dis+1);
            }
        }
        void init(int root,int n){
            dfs(root,-1,1);
            for(int j=1;j<LOG;j++){
                for(int i=1;i<=n;i++){
                    anc[i][j]=anc[ anc[i][j-1] ][j-1];
                }
            }
        }
        void swim(int &x,int h){
            for(int i=0;h>0;i++){
                if(h&1)
                    x=anc[x][i];
                h>>=1;
            }
        }
        int query(int x,int y){
            if(depth[x]<depth[y]) swap(x,y);
            swim(x,depth[x]-depth[y]);
            if(x==y) return x;
            for(int i=LOG-1;i>=0;i--){
                if(anc[x][i]!=anc[y][i]){
                    x=anc[x][i];
                    y=anc[y][i];
                }
            }
            return anc[x][0];
        }
    }lca;
    int main()
    {
        memset(head,-1,sizeof(head));
        cnt=0;
        int n,m,root;
        scanf("%d%d%d",&n,&m,&root);
        for(int i=0;i<n-1;i++){
            int a,b;
            scanf("%d%d",&a,&b);
            add_edge(a,b);
            add_edge(b,a);
        }
        lca.init(root,n);
        while(m--){
            int a,b;
            scanf("%d%d",&a,&b);
            printf("%d
    ",lca.query(a,b));
        }
        return 0;
    }
    

    dfs会爆栈?我们也可以用bfs进行预处理

    #include <bits/stdc++.h>
    #define maxn 1000005
    using namespace std;
    const int LOG=20;
    struct Node{
        int to,next;
    }q[maxn];
    int head[maxn],cnt=0;
    void add_edge(int from,int to){
        q[cnt].to=to;
        q[cnt].next=head[from];
        head[from]=cnt++;
    }
    struct LCA_ST{
        int anc[maxn][LOG],depth[maxn];
        void bfs(int root){
            queue<int>que;
            depth[root]=0;
            anc[root][0]=root;
            que.push(root);
            while(!que.empty()){
                int x=que.front();
                que.pop();
                for(int i=1;i<LOG;i++){
                    anc[x][i]=anc[ anc[x][i-1] ][i-1];
                }
                for(int i=head[x];i!=-1;i=q[i].next){
                    int to=q[i].to;
                    if(to==anc[x][0]) continue;
                    depth[to]=depth[x]+1;
                    anc[to][0]=x;
                    que.push(to);
                }
            }
        }
        void swim(int &x,int h){
            for(int i=0;h>0;i++){
                if(h&1)
                    x=anc[x][i];
                h>>=1;
            }
        }
        int query(int x,int y){
            if(depth[x]<depth[y]) swap(x,y);
            swim(x,depth[x]-depth[y]);
            if(x==y) return x;
            for(int i=LOG-1;i>=0;i--){
                if(anc[x][i]!=anc[y][i]){
                    x=anc[x][i];
                    y=anc[y][i];
                }
            }
            return anc[x][0];
        }
    }lca;
    int main()
    {
        memset(head,-1,sizeof(head));
        cnt=0;
        int n,m,root;
        scanf("%d%d%d",&n,&m,&root);
        for(int i=0;i<n-1;i++){
            int a,b;
            scanf("%d%d",&a,&b);
            add_edge(a,b);
            add_edge(b,a);
        }
        lca.bfs(root);
        while(m--){
            int a,b;
            scanf("%d%d",&a,&b);
            printf("%d
    ",lca.query(a,b));
        }
        return 0;
    }
    

    • 欧拉序+STST表(在线),预处理时间复杂度为O(n+nlogn)mathcal{O}(n+nlogn),每次询问的时间复杂度为O(1)mathcal{O}(1)
      个人认为,该算法是三个求解LCALCA算法中最浅显易懂的算法了。
      首先我们需要知道,欧拉序即为:对有根树TT进行深度优先遍历,无论是递归还是回溯,每次到达一个节点就把编号记录下来,得到一个长度为2N12*N-1的序列,成为树 TT 的欧拉序列 FF
      之后我们再记录一下出现的每个结点nodenode第一次出现的位置first(node)first(node),最后我们可以发现,LCA(u,v)LCA(u,v)等价于在欧拉序列中区间在first(u)first(u)first(v)first(v)之间的深度最小的结点。
      因此,此时我们就可以将求解LCALCA转化成一个RMQRMQ问题,继而,我们就可以用STST表进行维护。
      继而预处理的时间复杂度为O(n+nlogn)mathcal{O}(n+nlogn),而单次的查询可以达到O(1)mathcal{O}(1)
    #include <bits/stdc++.h>
    #define maxn 1000005
    using namespace std;
    const int LOG=20;
    struct Node{
        int to,next;
    }q[maxn<<1];
    int head[maxn],cnt=0;
    void add_edge(int from,int to){
        q[cnt].to=to;
        q[cnt].next=head[from];
        head[from]=cnt++;
    }
    struct LCA_ST{
        int ST[maxn<<1][LOG];
        int value[maxn],depth[maxn<<1],first[maxn],len;
        int cal(int x,int y){
            return depth[x]<depth[y]?x:y;
        }
        void dfs(int x,int fa,int dis){
            value[++len]=x,depth[len]=dis;
            first[x]=len;
            for(int i=head[x];i!=-1;i=q[i].next){
                int to=q[i].to;
                if(to==fa) continue;
                dfs(to,x,dis+1);
                value[++len]=x;
                depth[len]=dis;
            }
        }
        void init(int root){
            len=0;
            dfs(root,-1,1);
            for(int i=1;i<=len;i++) ST[i][0]=i;
            for(int j=1;(1<<j)<=len;j++){
                for(int i=1;i+(1<<j)-1<=len;i++){
                    ST[i][j]=cal(ST[i][j-1],ST[i+(1<<(j-1))][j-1]);
                }
            }
        }
        int query(int x,int y){
            int l=first[x],r=first[y];
            if(l>r) swap(l,r);
            int k=log2(r-l+1);
            return value[cal(ST[l][k],ST[r-(1<<k)+1][k])];
        }
    }lca;
    int main()
    {
        memset(head,-1,sizeof(head));
        cnt=0;
        int n,m,root;
        scanf("%d%d%d",&n,&m,&root);
        for(int i=0;i<n-1;i++){
            int a,b;
            scanf("%d%d",&a,&b);
            add_edge(a,b);
            add_edge(b,a);
        }
        lca.init(root);
        while(m--){
            int a,b;
            scanf("%d%d",&a,&b);
            printf("%d
    ",lca.query(a,b));
        }
        return 0;
    }
    
    • TarjanTarjan算法有待总结……
  • 相关阅读:
    nodejs MYSQL数据库执行多表查询
    【BZOJ3994】[SDOI2015]约数个数和 莫比乌斯反演
    【BZOJ2693】jzptab 莫比乌斯反演
    【BZOJ2154】Crash的数字表格 莫比乌斯反演
    【BZOJ2242】[SDOI2011]计算器 BSGS
    【BZOJ2005】[Noi2010]能量采集 欧拉函数
    【BZOJ1408】[Noi2002]Robot DP+数学
    【BZOJ2045】双亲数 莫比乌斯反演
    【BZOJ2186】[Sdoi2008]沙拉公主的困惑 线性筛素数
    【BZOJ4176】Lucas的数论 莫比乌斯反演
  • 原文地址:https://www.cnblogs.com/Chen-Jr/p/11007153.html
Copyright © 2020-2023  润新知