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\n",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\n",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\n",lca(u,v));
56     }
57     return 0;
58 }
倍增

 

 

 

posted @ 2017-08-09 14:24  ~Lanly~  阅读(421)  评论(0编辑  收藏  举报