OIIIIIIII

「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\n",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\n",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\n",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\n",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('\n');
      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('\n');
      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\n",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\n",fa[x]);
79     }
80     return 0;
81 }

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

posted @ 2018-10-09 21:19  qwertaya  阅读(279)  评论(0编辑  收藏  举报
MDZX
Changsha
Fulan