• 【テンプレート】LCA


    LCA目前比较流行的算法主要有tarjian,倍增和树链剖分

    1)tarjian

    是一种离线算法,需要提前知道所有询问对

    算法如下

      1.读入所有询问对(u,v),并建好树(建议邻接表)

      2.初始化每个节点各属一个并查集,都指向自己

      3.对整棵树进行dfs(深度优先搜索)遍历

    每处理到一个新节点(u)时看他的另一半(询问对象v)是否visit过,如果visit过了,则这组询问对的lca即v的并查集的根节点,若没有visit过,则继续向下深搜,该节点记为已visit

    每当回溯的时候都将子节点的并查集并到父节点的并查集中

    这样一遍走下来就完成了tarjian算法。

    超详细tarjain:orz

     


    2)树上倍增

    f[i,j]表示i的第2^j祖先dfs预处理f[i,j]=f[f[i,j-1],j-1];

    对于每一对x,y先将深度调成一样再枚举j逐一往上找,这两个过程都是log的

    超详细树上倍增:orz

     


    3)树剖

    树剖(树链剖分)是一种在线算法,跑起来非常快,应该是目前LCA算法中最优的

    建树后,我们需要把整棵树划为轻重链,

    每一个非叶子节点都一定在一条重链上

    定义:

    重边:父节点与其子树最大(子节点最多)的节点的连边称为重边

    轻边:非重边即为轻边

    重链:相连的重边称为重链

    划分重链后,我们要记一个jump数组表示存每个节点的“跳”的信息

    如果这个节点在重链上,则jump[i]为它所属重链的根节点(最顶端)

    如果这个节点不在重链上或者它是一条重链的顶端(根节点),那么jump[i]为它的父节点

    接下来我们就可以处理询问对了

    比如求两个节点a,b的LCA

    我们先看他们是否在同一条重链上,如果是,则LCA即为深度较小的节点

    如果不是,则我们需要比较jump[a]和jump[b]的深度,jump[a]比较浅则令a=jump[a]反之令b=jump[b]

    重复以上过程直到a==b(LCA为这个节点)或a,b在同一条重链上时(LCA为深度浅的节点)

    这样就完成了,复杂度虽说评是O(n*logn)但实际上跑起来快得多

    超详细树剖:orz

     


    练手题

    洛谷 P3379 【模板】最近公共祖先(LCA)

    题目描述

    如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

    输入输出格式

    输入格式:

    第一行包含三个正整数N、M、S,分别表示树的结点个数、询问的个数和树根结点的序号。

    接下来N-1行每行包含两个正整数x、y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树)。

    接下来M行每行包含两个正整数a、b,表示询问a结点和b结点的最近公共祖先。

    输出格式:

    输出包含M行,每行包含一个正整数,依次为每一个询问的结果。

    输入输出样例

    输入样例#1:
    5 5 4
    3 1
    2 4
    5 1
    1 4
    2 4
    3 2
    3 5
    1 2
    4 5
    输出样例#1:
    4
    4
    1
    4
    4
    

    说明

    时空限制:1000ms,128M

    数据规模:

    对于30%的数据:N<=10,M<=10

    对于70%的数据:N<=10000,M<=10000

    对于100%的数据:N<=500000,M<=500000

    样例说明:

    该树结构如下:

    第一次询问:2、4的最近公共祖先,故为4。

    第二次询问:3、2的最近公共祖先,故为4。

    第三次询问:3、5的最近公共祖先,故为1。

    第四次询问:1、2的最近公共祖先,故为4。

    第五次询问:4、5的最近公共祖先,故为4。

    故输出依次为4、4、1、4、4。

    LCA板子!!!

    1)tarjan

    #include <iostream>
    #include <cstring>
    #include <cstdio>
    using namespace std;
    
    const int N = 500005;
    const int M = 1000005;
    int top,cnt,dad[N],ans[N];
    bool used[N];
    
    struct heads {
        int head;
    }v1[N],v2[N];
    
    struct Edge {
        int v,next,num;
    }e1[M],e2[M];
    
    void chu()
    {
        memset(v1,-1,sizeof(v1));
        memset(v2,-1,sizeof(v2));
        memset(dad,-1,sizeof(dad));
        memset(used,0,sizeof(used));
    }
    
    int getdad(int x)
    {return dad[x] == -1 ? x : dad[x] = getdad(dad[x]);}
    
    void Unions(int a,int b)
    {
        int r1=getdad(a);
        int r2=getdad(b);
        if(r1!=r2)
            dad[r2]=r1;
    }
    
    void add1(int u,int v)
    {
        e1[top].v=v;
        e1[top].next=v1[u].head;
        v1[u].head=top++;
    }
    
    void add2(int u,int v,int i)
    {
        e2[cnt].num=i;
        e2[cnt].v=v;
        e2[cnt].next=v2[u].head;
        v2[u].head=cnt++;
    }
    
    void Tarjan(int u)
    {
        used[u]=true;
        for(int i=v1[u].head;i!=-1;i=e1[i].next)
        {
            int v=e1[i].v;
            if(used[v])
                continue;
            Tarjan(v);
            Unions(u,v);
        }
        int Ms;
        for(int i=v2[u].head;i!=-1;i=e2[i].next)
        {
            int v=e2[i].v;
            Ms=e2[i].num;
            if(used[v])///==getdad(v)
                ans[Ms]=getdad(v);
        }
    }
    
    int main()
    {
        int n,m,s,u,v;
        scanf("%d%d%d",&n,&m,&s);
        chu();
        int nn=n;
        n--;
        while(n--)
        {
            scanf("%d%d",&u,&v);
            add1(u,v),add1(v,u);
        }
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&u,&v);
            add2(u,v,i),add2(v,u,i);
        }
        Tarjan(s);
        for(int i=1;i<=nn;i++)
            printf("%d
    ",ans[i]);
        return 0;
    }
    Tarjan版(无注释...)

    2)树上倍增

    #include <iostream>
    #include <cstdio>
    #include <cmath>
    //maybe my English is not very good 
    
    using namespace std;
    
    const int M = 5e5 + 1;
    int n,m,s;
    int num; 
    int deep[M],h[M];
    bool vs[M];
    int jumps[M][21];
    int p;
    
    struct A{
        int next;
        int to;
    }t[M<<1];
    
    inline int read() //optimize
    {
        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;
    }
    
    void ADD(int x,int y) //connect the x and the y
    {
        num++;
        t[num].to=y;
        t[num].next=h[x];
        h[x]=num;
    }
    
    void Dfs(int u)
    {
        for(int i=h[u];i!=-1;i=t[i].next)
        {
            int v=t[i].to; //u's next side
            if(deep[v] == 0) //if v is not visited
            {
                deep[v]=deep[u]+1; //deep+1
                jumps[v][0]=u; //u is v's dad
                Dfs(v); //continue Dfs            
            }
        }
    }
    
    void steps()
    {
        p=int(log(n)/log(2)+0.001); //find the biggest
        for(int i=1;i<=p;i++) //the Limit
            for(int j=1;j<=n;j++)
                jumps[j][i]=jumps[jumps[j][i-1]][i-1];
    //the j jump 2^i can get to the (first jump 2^(i-1),then jump 2^i-1 can get to)
    //eh...I will speak in Chinese.
    //because 倍增 is use 次方的形式 increase
    }
    
    int LCA(int a,int b)
    {
        //We let the b's deep is small
        if(deep[a]<deep[b]) swap(a,b);
        for(int i=p;i>=0;i--)
        {//first let the a jump to the b's deep
            if(deep[jumps[a][i]]>=deep[b])
            a=jumps[a][i];
        }
        if(a == b) return b; //if the b is them's LCA , return b
        for(int i=p;i>=0;i--) //jump together
        {
            if(jumps[a][i]!=jumps[b][i])
            a=jumps[a][i],b=jumps[b][i]; //update
        }
        return jumps[a][0]; 
    }
    
    int main()
    {
        //s is the root
        n=read();m=read();s=read();
        for(int i=1;i<=n;i++) h[i]=-1;
        int x,y;
        for(int i=1;i<n;i++)
        {
            x=read();y=read();
            //connect the x and the y
            ADD(x,y);
            ADD(y,x);
        }
        deep[s]=1; //this is too important !!!
        //if you don't think so ,"//" it. 
        //and then you will know
        Dfs(s); //Dfs the root(s)
        steps(); //find the steps
        int a,b;
        while(m--)
        {
            a=read();b=read();
            printf("%d
    ",LCA(a,b));
        }
        return 0;
    }
    树上倍增英文版???
    #include<iostream>
    #include<cmath>
    #include<algorithm>
    #include<cstring>
    #include<cstdio>
    #include<stdio.h>
    #include<vector>
    #define maxn 500500
    using namespace std;
    ///隶属邻接表 
    struct Edge{                    //邻接表的结构体 
        int from,to;
    }edges[2*maxn];                 //边要乘2,因为是无向图 ; 
    int first[maxn],next[2*maxn];   //同理; 
    int read(){                        //读入优化,可以照着这个模板来写,这个还算写的比较好看。 
        int re=0;
        char ch=getchar();
        while (ch<'0' || ch>'9') ch=getchar();
        while (ch>='0' && ch<='9'){ 
            re=re*10+ch-'0'; 
            ch=getchar();
        }
        return re;
    }
    ///////////////////////////////////////////////
    ///全局变量 
    int n,m;
    int root;
    int height[maxn];
    float log2n;
    ///////////////////////////////////////////////////////
    ///隶属LCA的全局变量 
    int f[maxn][20];// 
    int have[maxn];                           //have,有没有找过,这都是套路 。 
    void dfs(int u,int h){                 //u代表点的标号,h代表高度。 
        int v;
        height[u]=h;
        for(int i=1;i<=log2n;i++) {
            if(h<=(1<<i)) break;              //由于i是从小到大计算的,故(1<<i)>=h 时可直接退出。请务必想清楚是<=  还是=。
            f[u][i] = f[ f[u][i-1] ][i-1]; //动规计算。同样也是一切倍增算法的核心。 
        }
        int k=first[u];
        while(k!=-1){
            v=edges[k].to;
            if(!have[v]) {
                have[v]=1;        
                f[v][0]=u;                 //将要找的下一个点的父节点标为当前处理的节点u。 
                dfs(v,h+1);
            }
            k=next[k];
        }
    }
    int require_LCA(int a,int b){
        int da=height[a],db=height[b];
    //第一步,将a,b两点移到同样的高度,只动高度大的那个点而不动高度小的那个点。 
        if(da!=db) {
            if(da<db){                   //保证a的高度是大于b的高度的。 
                swap(a,b);
                swap(da,db);
            }
            int d=da-db;
            for(int i=0;i<=log2n;i++) 
                if( (1<<i) & d) a=f[a][i]; //这里的位运算可以减少代码量
                                           //考虑到d是一个定值,而(1<<i)在二进制中只有第(i+1)位是1; 
                                           //那么d与(1<<i)如果某一位为1,那么表示可以向上移动, 
                                           //如果此时不移动,那么i增大了后就无法使height[a]==height[b]了 
        }
    //第二步,找到某个位置i,在这个位置时,f[a][i]!=f[b][i],但再向上移动一步,a,b相同了 
    //从log2n开始从大到小枚举i,如果超过了a,b的高度,则令i继续减小
    //如果没有超过a,b的高度,那么就判断移动了后会不会让a==b,
    //是,则i继续减小,否则,令此时的a=f[a][i],b=f[b][i]; 
        if(a==b) return b;
        int i=0;
        for(i=log2n;i>=0;i--) {
            if(height[ f[a][i] ]<0) continue;
            if( f[a][i]==f[b][i] ) continue;
            else a=f[a][i],b=f[b][i];        //顺便一提,在第二步任何地方没有break;
                                           //我就是因为在这里写了一个break,然后找了我两个小时啊。 
        }    
        return f[a][0];
    }
    /////////////////////////////////
    ///据说从主函数开始阅读是个好习惯。 
    int main(){
    //    freopen("in2.txt","r",stdin);
        n=read();m=read();root=read();
        memset(first,-1,sizeof(first));
        memset(next,-1,sizeof(next));
        int s,t;
        int dsd=2*(n-1);
        for(int i=1;i<=dsd;i+=2) {
            s=read();t=read();      //读入优化。 
            edges[i].from=s;
            edges[i].to=t;
            edges[i+1].from=t;
            edges[i+1].to=s;
            next[i]=first[s];
            first[s]=i;
            next[i+1]=first[t];
            first[t]=i+1;
        }
        // 以上是邻接表,在此不再赘述。 
        log2n=log(n)/log(2)+1;        //C++计算log是自然对数,我们要用的以2为底的对数,故要除以log(2); 
                                      //对无理数加上1或是0.5是个好习惯,可以减小误差; 
        memset(have,0,sizeof(have));
        memset(height,0,sizeof(height));
        memset(f,-1,sizeof(f));
        have[root]=1;                //fa[][]和height[]要在dfs理进行计算,不然根本找不到某个非根节点的父亲是谁; 
        dfs(root,1);                
        for(int i=1;i<=n;i++){
            for(int j=0;j<=log2n;j++) {
                if(height[i] <=(1<<j) ) break;
            }
        }
        for(int i=0;i<m;i++) {      //应对要求进行求解。 
            s=read();t=read();
            int y=require_LCA(s,t);
            printf("%d
    ",y);
        }
        return 0;
    }
    中文版23333

    3)树剖

    #include <iostream>
    #include <cstdio>
    #define _(ch) ch=read()                    //便于读入 
    
    using namespace std;
    
    const int S = 500001;
    bool f[S];                                 //dfs 标记
    int n,m,s;
    int fa[S];                                 //并查集 
    int num,h[S];                             //邻接表 
    int deep[S];                             //深度 
    int sum[S];                             //子结点个数 
    int dad[S];                             //链头元素 
    
    struct B{
        int to,next;
    }t[S<<1];
    
    inline int read()                         //optimize
    {
        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;  
    }
    
    void ADD(int x,int y)                     //connect the x and the y
    {
        num++;
        t[num].to=y;
        t[num].next=h[x];
        h[x]=num;
    }
    
    inline int Find(int x)
    //find the root (重链's top)
    {return fa[x] == x ? x : fa[x] = Find(fa[x]);}
    
    inline void Unions(int a,int b)
    {                                        //union(搭重链)
        /*int f1=Find(a);
        int f2=Find(b);
        if(f1!=f2)
        {
            fa[f1]=f2;
        }*/
        fa[Find(b)]=Find(a);
    }
    
    inline void dfs(int p)//D is the (结点)
    {//calc every D's son D
    //每个结点的深度 
        f[p]=true;
        int maxx=0;                            //寻找子节点中拥有子结点个数最多的节点编号 
        sum[0]=-1;                            //0号没有子结点
        for(int j=h[p];j;j=t[j].next) 
        {                                    //进行遍历 
            int v=t[j].to;
            if(f[v]) continue;
            deep[v]=deep[p]+1;
            dad[v]=p;                        //p is v's dad 
            dfs(v);                         //continue dfs
            if(sum[v] > sum[maxx]) maxx=v;     //update
            sum[p]+=sum[v]+1;
    //        p的子结点数 = p 的以'v'为根的子树的结点数目加上'v'这个点(即+1)
        }
        if(maxx) Unions(p,maxx);            //if updated 
        //that means find the (重链) succeed
    }
    
    inline int jump(int p)                     //find p can jump to  
    {
        int top=Find(p);                     //(重链)'s top 
        if(top == p) return dad[p];
    //    如果p所处于的链的链头就是自己,也就是说,已经位于链的top处,所以只能够跳到他的父结点的位置,
    //    所以直接return it's dad,即跳一步到达父结点处 
    //    说白了就是说,一定要跳!!! 
        return top;                            //其余情况就返回链头就好(就是当前结点跳到了链头位置) 
    }
    
    inline int Lca(int a,int b)             //Lca 
    {
        while(a!=b)                         //当两点不相等的时候就开始跳 
        {
            if(Find(a)==Find(b))             //如果它们位于同一条重链上 
              return deep[a]<deep[b] ? a:b; //直接返回深度较浅的那个点 
            int ja=jump(a),jb=jump(b);
            if(deep[ja] > deep[jb])            //如果a跳了之后没有到达b跳了之后的深度 
                a=ja;                        //就选取深度较深的点跳 
            else
                b=jb;
        }
        return a;
    }
    
    int main()
    {
        _(n),_(m),_(s);
        int x,y;
        for(int i=1;i<n;i++)
        {
            _(x),_(y);
            ADD(x,y),ADD(y,x);
            fa[i]=i;                        //顺便初始化一下并查集 
        }
        fa[n]=n;                             //有一个没有进行初始化的并查集,进行初始化 
        deep[s]=1;                             //根结点的深度设置为1,非常重要!!!! 
        dfs(s);                                //寻找子节点个数,位于哪一条重链上 
        while(m--)
        {
            _(x),_(y);
            printf("%d
    ",Lca(x,y));
        }
        return 0;
    }
    混杂版(才不是英语不好!)

    如果运气好也是错,那我倒愿意错上加错!

    ❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀

  • 相关阅读:
    nginx 跨域问题解决
    njs typescript 开发说明
    openrestyluatypes openresty typescript 类型定义
    使用 TypeScriptToLua 开发lua 应用
    如何编写一个简单的TypeScriptToLua lua 模块定义包
    docker sbom方便的软件物料清单扩展
    openresty + njs 提升系统nginx 的扩展性
    contentlayer 工作原理简单说明
    使用openrestyluatypes+TypeScriptToLua+testnginx 开发强类型的nginx lua 模块
    TypeScriptToLua npm 集成玩法
  • 原文地址:https://www.cnblogs.com/zxqxwnngztxx/p/6875427.html
Copyright © 2020-2023  润新知