zoukankan      html  css  js  c++  java
  • LCA模板

    Tarjan属于离线做法,即将问题全部储存起来后一起处理一起回答,相比于即问即答的在线做法,tarjan能仅通过一次DFS就能解决所有的LCA问题。

    具体很简单,运用了时间戳并查集

    visit数组记录某节点是否访问过,用f记录它的father是谁。

    先上伪代码

    Tarjan(u)//marge和find为并查集合并函数和查找函数
    {
        for each(u,v)    //访问所有u子节点v
        {
            Tarjan(v);        //继续往下遍历
            marge(u,v);    //合并v到u上
            标记v被访问过;
        }
        for each(u,e)    //访问所有和u有询问关系的e
        {
            如果e被访问过;
            u,e的最近公共祖先为find(e);
        }
    }

    从根节点不断往下遍历,回溯的时候将孩子和父亲合并,当一个节点i的所有儿子都遍历完了(包括儿子的儿子等等),就搜寻一遍询问看看能否回答一些,能够回答就回答,不能回答的之后再回答。

    至于能不能回答,取决于这个询问涉及到的另一个点是否已经访问过,如果还没访问,则暂时不能回答,如果访问了,则寻找另一个点的father,该father就是它们的最近公共祖先了。

    该算法之所以能够找到LCA,是因为一个已经的访问过的点A另一个正在访问的点B的LCA一定是一个还没结束访问的点C(就是还没遍历完孩子,没有和C的父亲合并的点)

     1 #include<iostream>
     2 #include<cstring>
     3 #include<cstdio>
     4 #define N 3000002
     5 using namespace std;
     6 int n,m,num1,num2,ans[N],f[N],u,v,head[N],qhead[N],visit[N];
     7 struct data2{
     8     int next,to,sign;   //链式前向星储存(其实sign可以不用) 
     9 }line[N],qline[N];
    10 void add1(int u,int v){
    11     num1++;
    12     line[num1].next=head[u];
    13     line[num1].to=v;
    14     line[num1].sign=num1;
    15     head[u]=num1;
    16     num1++;
    17     line[num1].next=head[v];
    18     line[num1].to=u;
    19     line[num1].sign=num1;
    20     head[v]=num1;
    21 }
    22 void add2(int u,int v){    //询问建立双向的链式前向星一方面方便查询询问,另一方面确保能回答到问题 
    23     num2++;
    24     qline[num2].next=qhead[u];
    25     qline[num2].to=v;
    26     qline[num2].sign=num2;
    27     qhead[u]=num2;
    28     num2++;
    29     qline[num2].next=qhead[v];
    30     qline[num2].to=u;
    31     qline[num2].sign=num2;
    32     qhead[v]=num2;
    33 }
    34 int find(int x){     //找父亲 
    35     if (f[x]==x) return x;
    36     f[x]=find(f[x]);
    37     return f[x];
    38 }
    39 void tarjan(int u){   //Tarjan求LCA 
    40     f[u]=u;    
    41     visit[u]=1;    //标记该点访问过 
    42     int v=0,e=0;
    43     for (int i=head[u];i!=0;i=line[i].next){    //遍历孩子 
    44         v=line[i].to;
    45         if (!visit[v]) {     //没有访问过的深搜 
    46             tarjan(v);
    47             f[v]=u;
    48         }
    49     }
    50     for (int i=qhead[u];i!=0;i=qline[i].next){     //遍历完该点的孩子后查询询问 
    51         v=qline[i].to;
    52         if (visit[v]){      //若该点访问过,则可以知道答案 
    53         e=find(v);
    54         if (qline[i].sign&1) ans[(qline[i].sign+1)/2]=e;
    55         else ans[qline[i].sign/2]=e;
    56         }
    57     }
    58 }
    59 int main(){
    60     memset(visit,0,sizeof(visit));
    61     scanf("%d%d",&n,&m);    //n个点,m个询问
    62     num1=0;
    63     num2=0;
    64     for (int i=1;i<=n-1;i++){    //建图 
    65         scanf("%d%d",&u,&v);
    66         add1(u,v);
    67     }
    68     for (int i=1;i<=m;i++){     //建立询问图 
    69         scanf("%d%d",&u,&v);
    70         add2(u,v);
    71     }
    72     tarjan(1);  //默认1为根节点 
    73     for (int i=1;i<=m;i++)
    74      printf("%d
    ",ans[i]);
    75     return 0;
    76 }
    Tarjan

    思想很简单,要细细品味。

    欧拉迹与dfs序类似,不过欧拉迹还记录了该点的出栈。

    有三个数组foot deep position,分别记录了欧拉迹,欧拉迹每个位置的点对应的深度和某点首次出现在欧拉迹里的位置。

    我们要找某两点的LCA,通过position数组定位到欧拉迹上,他们的LCA必定是深度最小的,所以就是求区间的最小值即可,我们可以用ST优化到O(1)

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<cstdlib>
     5 #include<algorithm>
     6 #include<cmath>
     7 #define N 1000001
     8 using namespace std;
     9 int n,m,p,head[N*2],next[N*2],to[N*2],visit[N],deep[N*2],ji[N*2],num,ti,f[N][100],qwq[N][100];
    10 void add(int u,int v){
    11     num++;
    12     next[num]=head[u];
    13     to[num]=v;
    14     head[u]=num;
    15     num++;
    16     next[num]=head[v];
    17     to[num]=u;
    18     head[v]=num;
    19 }
    20 void dfs(int x,int d){
    21     if (!visit[x]) visit[x]=++ti;
    22     ji[ti]=x;
    23     deep[ti]=d;
    24     for (int i=head[x],v;i!=0;i=next[i]){
    25         v=to[i];
    26         if (!visit[v]) {
    27             dfs(v,d+1);
    28             ji[++ti]=x;
    29             deep[ti]=d;    
    30         }
    31     }
    32 }
    33 void ST(){
    34     int tmp=(int)(log(ti)/log(2));
    35     for (int i=1;i<=ti;i++){
    36      f[i][0]=deep[i];
    37      qwq[i][0]=ji[i];
    38     }
    39     for (int j=1;j<=tmp;j++)
    40      for (int i=1;i<=ti;i++){
    41          int k=1<<(j-1);
    42          if (i-k<=ti)
    43          if (f[i][j-1]<f[i+k][j-1]){
    44              f[i][j]=f[i][j-1];
    45              qwq[i][j]=qwq[i][j-1];
    46          }
    47          else{
    48              f[i][j]=f[i+k][j-1];
    49              qwq[i][j]=qwq[i+k][j-1];
    50          }
    51      }
    52 }
    53 int lca(int x,int y){
    54     int a=min(visit[x],visit[y]);
    55     int b=max(visit[y],visit[x]);
    56     int tmp=(int)(log((b-a)+1)/log(2));
    57     if (f[a][tmp]<f[b-(1<<(tmp))+1][tmp])
    58      return qwq[a][tmp];
    59      else return qwq[b-(1<<(tmp))+1][tmp];
    60 }
    61 int main(){        
    62     scanf("%d%d",&n,&m);
    63     for (int i=1,u,v;i<n;i++){
    64         scanf("%d%d",&u,&v);
    65         add(u,v);
    66     }
    67     dfs(1,0);
    68     ST();
    69     for (int i=1,u,v;i<=m;i++){
    70         scanf("%d%d",&u,&v);
    71         printf("%d
    ",lca(u,v));
    72     }
    73     return 0;
    74 }
    欧拉迹

    倍增也是另一种求LCA的方法。

    朴素我们是根据深度让点一步一步地往上跳,知道他们到的点相同。

    很显然一步一步太耗时间了,我们可以一次跳好几步。

    由二进制拆分可知其步数必能拆成几个2i相加,故用up[i][j]表示i点往上跳2j格后到哪个点。

    我们先让深度深的点往上跳,直到两点深度相同或相近时,再让两个点一起跳即可。

    最后他们的父亲就是LCA了。

     1 #include<cstring>
     2 #include<cstdio>
     3 #include<iostream>
     4 #define N 1000050
     5 using namespace std;
     6 int n,m,head[N],next[N],to[N],fa[N],up[N][32],deep[N],num,root;
     7 void add(int u,int v){
     8     num++;
     9     next[num]=head[u];
    10     to[num]=v;
    11     head[u]=num;
    12     num++;
    13     next[num]=head[v];
    14     to[num]=u;
    15     head[v]=num;
    16 }
    17 void dfs(int u){
    18     up[u][0]=fa[u];
    19     for (int i=1;i<=31;i++)
    20         up[u][i]=up[up[u][i-1]][i-1];
    21     for (int i=head[u];i;i=next[i]){
    22         int v=to[i];
    23         if (v!=fa[u]){
    24             fa[v]=u;
    25             deep[v]=deep[u]+1;
    26             dfs(v);
    27         }
    28     }
    29 }
    30 int lca(int u,int v){
    31     if (deep[u]<deep[v]) swap(u,v);
    32     for (int i=31;i>=0;i--)
    33         if (deep[v]<=deep[up[u][i]])
    34         u=up[u][i];
    35     if (v==u) return u;
    36     for (int i=31;i>=0;i--)
    37         if (up[u][i]!=up[v][i]){   //一起跳
    38             u=up[u][i];
    39             v=up[v][i];
    40         }
    41     return up[u][0];
    42 }
    43 int main(){
    44     scanf("%d%d%d",&n,&m,&root);   //root为根
    45     memset(fa,127,sizeof(fa));
    46     num=0;
    47     for (int i=1,u,v;i<n;i++){
    48         scanf("%d%d",&u,&v);
    49         add(u,v);
    50     }
    51     deep[root]=1;fa[root]=root;
    52     dfs(root);
    53     for (int i=1,u,v;i<=m;i++){
    54         scanf("%d%d",&u,&v);
    55         printf("%d
    ",lca(u,v));
    56     }
    57     return 0;
    58 }
    倍增

     

  • 相关阅读:
    Kubernetes源码client-go的workqueue
    (转)Golang语言heap剖析及利用heap实现优先级队列
    (转)Kubernetes GC设计原则
    Kubernetes kubefed使用Cluster Selector
    使用kubeadm添加新节点到集群及相关问题解决
    Kubeadm颁发证书延迟到10年
    kubeadm升级Kubernetes证书
    混合kubebuilder与code generator编写CRD
    (转)Go项目的vendor目录是否需要提交?看这一篇就知道了
    Java 将Excel转为et和ett格式
  • 原文地址:https://www.cnblogs.com/Lanly/p/7325069.html
Copyright © 2011-2022 走看看