• 最近公共祖先LCA(我肯定是你的LCA)


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

    例子

    LCA(13,14)=12

    LCA(13,7)=3

    LCA(7,11)=2

    LCA(11,2)=2

    好了,了解以后我们就来康康怎么求吧

    暴力LCA

    如果找LCA(6,8),我们就要先让它们的深度相等。此时(deep[1]=0)deep[6]=4,deep[8]=3,我们就先让节点6爬到节点5

    然后就是愉快的一起爬

    让它们一层一层地向上爬,知道爬到同一节点

    但是都知道,一旦这棵树有点高,like this,就会TTT

     

    因为这样爬着太慢了(青蛙爬树都知道跳着爬)

     倍增LCA(在线操作)

    所以我们升级了,会跳着爬了

    但是怎么升呢,大家都知道,计算机和二进制都离不开关系

    所以当然是按照2的次方来跳,不过我们不是从小往大(1,2,4,8,16,32,64...),而是从大到小(...64,,32,16,8,4,2,1)(因为从小到大加着会超过,到时候很麻烦)

    所以我们跳的时候就还要计算一下你这么跳会到哪个节点去(不然你乱跳啊)

    总所周知,1+1=2(这不是废话吗)

     1 void dfs(int x,int fa)
     2 {
     3     deep[x]=deep[fa]+1;
     4     f[x][0]=fa;
     5     for(int i=1;(1<<i)<=deep[x];i++)
     6     {
     7         f[x][i]=f[f[x][i-1]][i-1];
     8     }
     9     for(int i=0;i<mp[x].size();i++)
    10     {
    11         if(mp[x][i]==fa)continue;
    12         dfs(mp[x][i],x);
    13     }
    14 }


    我们用f[i][j]表示第i个节点向上跳2^j高度后的祖先节点,我们就不用像个憨批一样去搜了(不然你和暴力有什么区别),他就等于2^(j-1)*2也就是向上跳两次2^(j-1)

    所以f[i][j]=f[f[i][]j-1][j-1]

    所以我们循坏j从小到大,这样就可以用小的来递推大的了

    然后搜索的时候顺便把深度也计算了

    预处理完了,我们就要开始干正事了

     1 int LCA(int x,int y)
     2 {
     3     if(deep[x]<deep[y])swap(x,y);
     4     for(int i=log2(deep[x]);i>=0;i--)
     5         if(deep[x]-(1<<i)>=deep[y])
     6             x=f[x][i];
     7     if(x==y)return x;
     8     for(int i=log2(deep[x]);i>=0;i--)
     9     {
    10         if(f[x][i]!=f[y][i])
    11         {
    12             x=f[x][i];
    13             y=f[y][i];
    14         }
    15     }
    16     return f[x][0];
    17 }

    我们先让两个节点到达同一高度(为了方便计算,我们把高的放前面)

    然后向上爬

    先判断是否在同一点(因为存在两者的祖先是其中一个节点的可能,就是你和你爸的最近公共祖先是你爸)

    然后一起向上跳跳跳(jump!!!)

    为了避免两者计算出的公共祖先不是最近的(例如你爷爷也是你和你爸的公共祖先)

    我们只跳到公共祖先的下一层,也就是第十排的判断

    最后再输出它们的最近公共祖先就好了

     1 #include<bits/stdc++.h>
     2 #define N 500005
     3 using namespace std;
     4 int n,m,root;
     5 int f[N][21];
     6 int deep[N];
     7 vector<int> mp[N];
     8 inline int read(){
     9     int x=0,f=1;
    10     char ch=getchar();
    11     while(ch<'0'||ch>'9'){
    12         if(ch=='-')
    13             f=-1;
    14         ch=getchar();
    15     }
    16     while(ch>='0'&&ch<='9'){
    17         x=(x<<1)+(x<<3)+(ch^48);
    18         ch=getchar();
    19     }
    20     return x*f;
    21 }
    22 void dfs(int x,int fa)
    23 {
    24     deep[x]=deep[fa]+1;
    25     f[x][0]=fa;
    26     for(int i=1;(1<<i)<=deep[x];i++)
    27     {
    28         f[x][i]=f[f[x][i-1]][i-1];
    29     }
    30     for(int i=0;i<mp[x].size();i++)
    31     {
    32         if(mp[x][i]==fa)continue;
    33         dfs(mp[x][i],x);
    34     }
    35 }
    36 int LCA(int x,int y)
    37 {
    38     if(deep[x]<deep[y])swap(x,y);
    39     for(int i=log2(deep[x]);i>=0;i--)
    40         if(deep[x]-(1<<i)>=deep[y])
    41             x=f[x][i];
    42     if(x==y)return x;
    43     for(int i=log2(deep[x]);i>=0;i--)
    44     {
    45         if(f[x][i]!=f[y][i])
    46         {
    47             x=f[x][i];
    48             y=f[y][i];
    49         }
    50     }
    51     return f[x][0];
    52 }
    53 int main()
    54 {
    55     deep[0]=-1;
    56     n=read(),m=read(),root=read();
    57     for(int i=1;i<n;i++)
    58     {
    59         int a,b;
    60         a=read(),b=read();
    61         mp[a].push_back(b);
    62         mp[b].push_back(a);
    63     }
    64     dfs(root,0);
    65     while(m--)
    66     {
    67         int a,b;
    68         a=read(),b=read();
    69         printf("%d
    ",LCA(a,b));
    70     }
    71     return 0;
    72 }

     Tarjan求LCA(离线操作)

      首先,我们来理解一下离线操作。就是输入完问题后一次性解决。而在线操作就是每输入一个问题就可以立即得出答案。其实你硬要把离线做成在线也行(但是每次都要初始化,很麻烦。离线之所以是离线,说明做的方法有共通之处,有些一次就可以解决)

      首先我们需要的数组如下

      

    flag []表示是否已经回溯(被搜索过)
    fa[]寻找父亲节点
    ans[]记录每个问题的答案
    e[]链式前向星存边
    q[]记录问题的vector

    注意,这里的问题要存两个,具体原因我后面会讲到

     我们用的是DFS(深度优先搜索),因为公共祖先是父亲节点嘛,这样的话保证一个点之前的都是自己的祖先(能懂吧,毕竟只是一棵树)

    看看问题

    3 8
    3 11
    6 9
    5 9
    7 8

    因为8,11都未被搜索,所以不做操作,但是回溯的时候要更新fa[]。因为初始值都是自己,回溯才连接父亲节点

    此时可以用并查集,方便路径压缩 当然你要暴力也没意见

     

    同理

     

    不做操作

     

    关键的来了,众所周知,最近公共祖先就是两个点的祖先节点所构成的链的最先相交的地方,此时蓝色的都是8的父亲,所以我们只需要搜索另外一个点的父亲,而这个操作就可以考虑并查集,当然不用也行。(其实都差不多)一层一层向上搜索,直到一个fa[]等于自己的节点。

    可以看出,fa[]等于自己的点要不就是正在搜索没有回溯(也就是当前节点的祖先),要不就是没有被搜索。

    怎么保证第一个搜索的一定是蓝色的呢。我们可以假设第一个fa[]等于自己的是没有被搜索的,但是如果没有被搜索的话这个点也不会被搜索,说明这个点的fa[]也等于自己,这是矛盾的,所以不成立。

    所以LCA(3,8)=1

    但是这里并不能计算 7,8.因为此时7并没有被打上标记(黄色)

     

     

    LCA(3,11)=1

    LCA(5,9)=1

    LCA(6,9)=1

    LCA(7,8)=7'

     

     好了,这样就做完了时间复杂度差不多是O(n+q)

    康康代码

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 const int N=500010;
     4 const int M=500010;
     5 int n,m,s,cnt;
     6 int last[N];//前向星
     7 int fa[N];//记录父亲
     8 int ans[M];//记录答案
     9 bool flag[N];//判断是否被搜索
    10 struct node{//记录边的
    11     int to,pre;
    12 }e[2*M];
    13 struct question{//还要记录编号,方便保存并且输出答案
    14     int to,num;
    15 };
    16 vector<question>q[N];//记录问题
    17 void add(int x,int y)
    18 {
    19     cnt++;
    20     e[cnt].to=y;
    21     e[cnt].pre=last[x];
    22     last[x]=cnt;
    23 }
    24 int get(int x)//找父亲节点
    25 {
    26     return fa[x]==x?x:(fa[x]=get(fa[x]));
    27 }
    28 void dfs(int u,int root)//最核心部分
    29 {
    30     for(int i=last[u];i;i=e[i].pre)
    31     {
    32         if(e[i].to==root)continue;
    33         dfs(e[i].to,u);
    34         fa[e[i].to]=u;
    35     }
    36     for(int i=0;i<q[u].size();i++)
    37         if(flag[q[u][i].to])
    38             ans[q[u][i].num]=get(q[u][i].to);
    39     flag[u]=1;
    40 }
    41 int main()
    42 {
    43     scanf("%d%d%d",&n,&m,&s);
    44     for(int i=1;i<=n;i++)fa[i]=i;
    45     for(int i=1;i<n;i++)
    46     {
    47         int u,v;
    48         scanf("%d%d",&u,&v);
    49         add(u,v);
    50         add(v,u);
    51     }
    52     for(int i=1;i<=m;i++)
    53     {
    54         int x,y;
    55         scanf("%d%d",&x,&y);
    56         q[x].push_back((question){y,i});//一定要存两次,因为你不知道那个先被搜索到
    57         q[y].push_back((question){x,i});
    58     }
    59     dfs(s,0);
    60     for(int i=1;i<=m;i++)
    61     {
    62         printf("%d
    ", ans[i]);//避免TTT
    63     }
    64     return 0;
    65 }

    好了,就是这样了。喜欢的话点赞支持一下哦

  • 相关阅读:
    生成函数代替伯努利数
    关于费用流
    GDOI注意事项
    计算几何 学习笔记
    jzoj5370
    图上的游戏
    小学生语文题
    arcane
    P2305 [NOI2014] 购票
    P3512 [POI2010]PIL-Pilots
  • 原文地址:https://www.cnblogs.com/hualian/p/11918681.html
Copyright © 2020-2023  润新知