zoukankan      html  css  js  c++  java
  • 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算法有待总结……
  • 相关阅读:
    基于51单片机PWM调速L298芯片控制两选一直流电机正反运转的项目工程
    基于51单片机四位一体数码管显示短按加或减数值及长按连加或连减数值的项目工程
    Android 多语言动态更新方案探索
    图解 Promise 实现原理(一)—— 基础实现
    vivo 大规模特征存储实践
    揭秘 vivo 如何打造千万级 DAU 活动中台
    前端科普系列(2):Node.js 换个角度看世界
    分布式定时任务调度框架实践
    深入学习和理解 Redux
    前端科普系列(1):前端简史
  • 原文地址:https://www.cnblogs.com/Chen-Jr/p/11007153.html
Copyright © 2011-2022 走看看