• 「LuoguP3379」 【模板】最近公共祖先(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有几种常见的做法

    • 暴力跳
      • 先把较深的往上跳,跳到同一深度
      • 然后一起跳
      • 单次复杂度$O(n)$分分钟带你上天
    • 倍增
      • 在跳的时候优化一下,不一格一格的跳,而是拆分成二进制跳
      • 是对暴力跳选手思维难度上最友好的升级方式
      • 需要预处理出每个点往上$2^i$步的祖先,
      • 时间复杂度$O(nlogn+mlogn)$,空间复杂度$O(nlogn)$
      •  1 /*
         2     qwerta
         3     P3379 【模板】最近公共祖先(LCA)
         4     Accepted
         5     100
         6     代码 C++,1.37KB
         7     提交时间 2018-03-13 18:33:35
         8     耗时/内存
         9     1672ms, 51789KB
        10 */
        11 #include<iostream>
        12 #include<cstdio>
        13 #include<algorithm>
        14 using namespace std;
        15 struct emm{
        16     int f,e;
        17 }a[1000007];
        18 int h[500007];
        19 int d[500007];
        20 int p[500007][20];
        21 void dfs(int no,int fa)
        22 {
        23     d[no]=d[fa]+1;
        24     //cout<<no<<" "<<d[no]<<" "<<fa<<endl;
        25     p[no][0]=fa;
        26     int w;
        27     for(w=1;w<20;w++)
        28     p[no][w]=p[p[no][w-1]][w-1];
        29     for(w=h[no];w;w=a[w].f)
        30     {
        31         if(a[w].e!=fa)
        32         dfs(a[w].e,no);
        33     }
        34     return;
        35 }
        36 int main()
        37 {
        38     int c=0,x,y,n,m,s,i,j;
        39     scanf("%d%d%d",&n,&m,&s);
        40     for(i=1;i<n;++i)
        41     {
        42         scanf("%d%d",&x,&y);
        43         ++c;
        44         a[c].f=h[x];
        45         h[x]=c;
        46         a[c].e=y;
        47         ++c;
        48         a[c].f=h[y];
        49         h[y]=c;
        50         a[c].e=x;
        51         d[i]=99999999;
        52     }
        53     d[n]=99999999;
        54     dfs(s,0);
        55     for(i=1;i<=m;++i)
        56     {
        57         scanf("%d%d",&x,&y);
        58         if(d[x]<d[y])swap(x,y);
        59         for(j=19;j>=0;--j)
        60         {
        61             if((d[x]-d[y])>=(1<<j))
        62             {
        63                 x=p[x][j];
        64                 //cout<<x<<" ";
        65             }
        66         }
        67         if(x==y)printf("%d
        ",x);
        68         else{
        69         for(j=19;j>=0;--j)
        70         {
        71             if(p[x][j]!=p[y][j])
        72             {
        73                 x=p[x][j];
        74                 y=p[y][j];
        75             }
        76         }
        77         printf("%d
        ",p[x][0]);}
        78     }
        79     /*
        80     for(i=1;i<=n;i++)
        81     {
        82     cout<<i<<" ";
        83     for(j=0;j<=19;j++)
        84     cout<<p[i][j]<<" ";
        85     cout<<endl;
        86     }*/
        87     return 0;
        88 }
        89 
        90 倍增求LCA
        倍增求LCA
    • ST表
      • 原理:dfs序在这两点之间 的点中,深度最小的点为lca
      • 所以记录dfs序和深度,在区间上找最小值,转化为RMQ问题。
      • 需要预处理dfs序和ST表。
      • 时间复杂度$O(n+nlogn+mlogn)$,空间复杂度$O(3*n+nlogn)$
      • 理论上比倍增慢一丢丢。
      •   1 /*
          2     qwerta
          3     P3379 【模板】最近公共祖先(LCA)
          4     Accepted
          5     100
          6     代码 C++,2.28KB
          7     提交时间 2018-06-24 11:36:03
          8     耗时/内存
          9     1992ms, 98929KB
         10 */
         11 #include<cstdio>
         12 using namespace std;
         13 int n,m,s;
         14 struct emm{
         15     int e,f;
         16 }a[1000007];
         17 int h[500007];
         18 int c;
         19 inline int read()
         20 {
         21     int x=0;
         22     char c=getchar();
         23     while(c<'0'||c>'9') c=getchar();
         24     while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
         25     return x;
         26 }
         27 inline void write(int x)
         28 {
         29     if(x>9) write(x/10);
         30     putchar(x%10+'0');
         31     return;
         32 }
         33 inline int min(int qwq,int qaq){return qwq<qaq?qwq:qaq;}
         34 inline void swap(int &qq,int &ww){int ee=qq;qq=ww;ww=ee;return;}
         35 inline void con(int q,int w)
         36 {
         37     a[++c].f=h[q];
         38     h[q]=c;
         39     a[c].e=w;
         40     return;
         41 }
         42 inline void scan()
         43 {
         44     n=read(),m=read(),s=read();
         45     //scanf("%d%d%d",&n,&m,&s);
         46     int x,y;
         47     for(register int i=1;i<n;++i)
         48     {
         49         x=read(),y=read();
         50         //scanf("%d%d",&x,&y);
         51         con(x,y);
         52         con(y,x);
         53     }
         54     return;
         55 }
         56 int fir[500007];
         57 int pl[1000007];
         58 int d[1000007];
         59 int f[1000007][21];
         60 int dd;
         61 void dfs(int x)
         62 {
         63     d[x]=++dd;
         64     pl[++c]=x;
         65     //printf("%d %d %d %d
        ",c,x,d[c],pl[c]);
         66     if(!fir[x])fir[x]=c;
         67     for(register int i=h[x];i;i=a[i].f)
         68     {
         69         int u=a[i].e;
         70         if(!fir[u])
         71         {
         72             dfs(u);
         73             pl[++c]=x;
         74             //printf("%d %d %d %d
        ",c,x,d[c],pl[c]);
         75         }
         76     }
         77     --dd;
         78     return;
         79 }
         80 inline void rmq()
         81 {
         82     for(register int i=1;i<=c;++i)
         83     f[i][0]=pl[i];
         84     for(register int j=1;(1<<j)<=c;++j)
         85     for(register int i=1;i+(1<<j)-1<=c;++i)
         86     {
         87         if(d[f[i][j-1]]<d[f[i+(1<<(j-1))][j-1]])
         88         f[i][j]=f[i][j-1];
         89         else f[i][j]=f[i+(1<<(j-1))][j-1];
         90     }
         91     return;
         92 }
         93 inline void predo()
         94 {
         95     c=0;
         96     dfs(s);
         97     rmq();
         98     return;
         99 }
        100 inline void find(int x,int y)
        101 {
        102     int l=fir[x],r=fir[y];
        103     if(l>r)swap(l,r);
        104     int p;
        105     //cout<<l<<" "<<r<<" ";
        106     for(register int j=20;j>=0;--j)
        107     if(l+(1<<j)-1<=r)
        108     {
        109         //cout<<f[l][j]<<" "<<f[r-(1<<j)+1][j]<<endl;
        110         //cout<<l<<" "<<r<<" "<<j<<endl;
        111         p=d[f[l][j]]<d[f[r-(1<<j)+1][j]]
        112         ?f[l][j]:f[r-(1<<j)+1][j];
        113         //p=min(f[l][j],f[r-(1<<j)+1][j]);
        114         write(p);putchar('
        ');
        115         return;
        116     }
        117     //cout<<"a"<<endl;
        118     return;
        119 }
        120 inline void run()
        121 {
        122     for(register int i=1;i<=m;++i)
        123     {
        124         int x,y;
        125         scanf("%d%d",&x,&y);
        126         find(x,y);
        127     }
        128     return;
        129 }
        130 int main()
        131 {
        132     scan();
        133     predo();
        134     run();
        135     return 0;
        136 }
        ST表求LCA
    • tarjan
      • 和求强连通分量的tarjan不是一个东西。
      • 不太了解,应该是最小众的做法了叭,据说有常数上的优势?
      • 其实以前是听懂过的,但是仗着自己已经会两种做法了就飘了没写
    • 树链剖分
      • 乍一听挺二了吧唧的,以前觉得像各种A+B的题解一样装哔
      • 但是自从会了树剖之后我的倍增和ST表就忘光了a!(不知道该开心还是难过
      • 对于会树剖的选手而言,又好写又不用过脑子还快。
      • 需要预处理遍历两遍,和做一个gettop的操作,正式跑的过程应该比倍增和ST快。
      • 反正我写出来快了不少。
      •  1 /*
         2     qwerta
         3     P3379 【模板】最近公共祖先(LCA)
         4     Accepted
         5     100
         6     代码 C++,1.48KB
         7     提交时间 2018-10-09 19:16:23
         8     耗时/内存
         9     1043ms, 20392KB
        10 */
        11 #include<cstdio>
        12 #include<iostream>
        13 using namespace std;
        14 #define R register
        15 const int MAXN=500007;
        16 struct emm{
        17     int e,f;
        18 }a[2*MAXN];
        19 int h[MAXN];
        20 int tot=0;
        21 void con(int x,int y)
        22 {
        23     a[++tot].f=h[x];
        24     h[x]=tot;
        25     a[tot].e=y;
        26     a[++tot].f=h[y];
        27     h[y]=tot;
        28     a[tot].e=x;
        29     return;
        30 }
        31 int fa[MAXN],d[MAXN],siz[MAXN],z[MAXN],top[MAXN];
        32 void dfs(int x)
        33 {
        34     siz[x]=1;
        35     top[x]=x;
        36     int mac=0,macc=-1;
        37     for(R int i=h[x];i;i=a[i].f)
        38     if(!d[a[i].e])
        39     {
        40         d[a[i].e]=d[x]+1;
        41         fa[a[i].e]=x;
        42         dfs(a[i].e);
        43         siz[x]+=siz[a[i].e];
        44         if(siz[a[i].e]>macc){mac=a[i].e;macc=siz[a[i].e];}
        45     }
        46     z[x]=mac;
        47     top[mac]=x;
        48     return;
        49 }
        50 int fitop(int x)
        51 {
        52     if(top[x]==x)return x;
        53     return top[x]=fitop(top[x]);
        54 }
        55 inline int read()
        56 {
        57     char ch=getchar();
        58     int x=0;
        59     while(!isdigit(ch))ch=getchar();
        60     while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
        61     return x;
        62 }
        63 void write(int x)
        64 {
        65     if(x>9)write(x/10);
        66     putchar(x%10+'0');
        67     return;
        68 }
        69 int main()
        70 {
        71     //freopen("a.in","r",stdin);
        72     int n=read(),m=read(),s=read();
        73     for(R int i=1;i<n;++i)
        74     {
        75         int x=read(),y=read();
        76         con(x,y);
        77     }
        78     d[s]=1;
        79     dfs(s);
        80     for(R int i=1;i<=n;++i)
        81     top[i]=fitop(i);
        82     for(R int c=1;c<=m;++c)
        83     {
        84         int u=read(),v=read();
        85         while(top[u]!=top[v])
        86         {
        87             if(d[top[u]]>d[top[v]])u=fa[top[u]];
        88             else v=fa[top[v]];
        89         }
        90         write(d[u]<d[v]?u:v);
        91         putchar('
        ');
        92     }
        93     return 0;
        94 }
        树剖求LCA

    也许还有别的做法叭,太弱了不了解。

    吸氧?卡常?不存在的我跟你说,

    吸氧是不可能的,这辈子都不可能的,老子复杂度这么优秀吸什么氧?!

    ——我屮艸芔茻加个register吸个氧就减到三分之二?!这么香?!!

    总结

    在倍增和ST之间推荐倍增,思维难度低,效率还蛮不错。

    但是也见过考试考RMQ问题的...我校倍增选手当场哭出声2333

    然后会了树剖还写这些个毛,多难想啊2333

    其实我只是放一下代码的qwq

    UPD

    我校选手看过来!

    这里是我下午当场出锅的代码(qaq

     1 /*
     2     qwerta
     3     P3379 【模板】最近公共祖先(LCA)
     4     Accepted
     5     100
     6     代码 C++,1.18KB
     7     提交时间 2018-10-14 16:54:45
     8     耗时/内存
     9     2037ms, 53444KB
    10 */
    11 #include<cstdio>
    12 #include<iostream>
    13 #include<algorithm>
    14 using namespace std;
    15 #define R register
    16 struct emm{
    17     int to,nxt;
    18 }a[1000003];
    19 int h[500003];//邻接链表存图
    20 int cnt=0;
    21 inline void con(int x,int y)//连边
    22 {
    23     a[++cnt].nxt=h[x];
    24     h[x]=cnt;
    25     a[cnt].to=y;
    26     a[++cnt].nxt=h[y];
    27     h[y]=cnt;
    28     a[cnt].to=x;
    29     return;
    30 }
    31 int fa[500003],d[500003];
    32 void dfs(int x)//dfs建树
    33 {
    34     for(R int i=h[x];i;i=a[i].nxt)
    35     if(!d[a[i].to])
    36     {
    37         d[a[i].to]=d[x]+1;
    38         fa[a[i].to]=x;
    39         dfs(a[i].to);
    40     }
    41     return;
    42 }
    43 int la[500003][20];//向上2^j步的祖先
    44 int main()
    45 {
    46     int n,m,s;
    47     scanf("%d%d%d",&n,&m,&s);
    48     for(R int i=1;i<n;++i)
    49     {
    50         int x,y;
    51         scanf("%d%d",&x,&y);//读边
    52         con(x,y);
    53     }
    54     d[s]=1;
    55     fa[s]=s;
    56     dfs(s);
    57     for(R int i=1;i<=n;++i)
    58       la[i][0]=fa[i];
    59     for(R int j=1;j<=19;++j)
    60     for(R int i=1;i<=n;++i)
    61       la[i][j]=la[la[i][j-1]][j-1];
    62     //cout<<endl;
    63     for(R int i=1;i<=m;++i)
    64     {
    65         int x,y;
    66         scanf("%d%d",&x,&y);
    67         if(d[x]<d[y])swap(x,y);
    68         //
    69         for(R int j=19;j>=0;--j)
    70         if(d[x]-(1<<j)>=d[y])
    71           x=la[x][j];
    72         //same depth
    73         if(x==y){printf("%d
    ",y);continue;}
    74         for(R int j=19;j>=0;--j)
    75         if(la[x][j]!=la[y][j])
    76           x=la[x][j],y=la[y][j];
    77         //cout<<x<<" "<<y<<" "<<endl;
    78         printf("%d
    ",fa[x]);
    79     }
    80     return 0;
    81 }

    都追到这里了要给老学姐点个关注吖!QAQ

  • 相关阅读:
    php 计算概率,可以任意添加
    如何绕过浏览器的弹窗拦截机制
    javascript iframe 操作(一)
    视频学习站
    技术博文
    js如何打印对象
    云主机
    cookie小细节
    cookie细节
    实用网址
  • 原文地址:https://www.cnblogs.com/qwerta/p/9762958.html
Copyright © 2020-2023  润新知