最近公共祖先(LCA)模板

以下转自:https://www.cnblogs.com/JVxie/p/4854719.html

首先是最近公共祖先的概念(什么是最近公共祖先?):

在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先,就是两个节点在这棵树上深度最大公共祖先节点

换句话说,就是两个点在这棵树上距离最近的公共祖先节点

所以LCA主要是用来处理当两个点仅有唯一一条确定的最短路径时的路径。

有人可能会问:那他本身或者其父亲节点是否可以作为祖先节点呢?

答案是肯定的,很简单,按照人的亲戚观念来说,你的父亲也是你的祖先,而LCA还可以将自己视为祖先节点

举个例子吧,如下图所示最近公共祖先是2最近公共祖先最近公共祖先。 

这就是最近公共祖先的基本概念了,那么我们该如何去求这个最近公共祖先呢?

通常初学者都会想到最简单粗暴的一个办法:对于每个询问,遍历所有的点,时间复杂度为O(n*q),很明显,n和q一般不会很小

常用的求LCA的算法有:Tarjan(离线)、倍增法(在线)

下面贴两种写法的代码,以HDU 2586 How far away ?为例:

题目大意:

给你一棵树n个节点,m次询问,每次询问树上节点u和v的最小距离。

Tarjan(时间复杂度O(n+Qlogn))

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<vector>
 6 using namespace std;
 7 const int N=4e4+5;
 8 
 9 struct node{
10     int to,w;
11     node(int to,int w):to(to),w(w){}
12 };
13 
14 struct qnode{
15     int to,id;
16     qnode(int to,int id):to(to),id(id){}
17 };
18 
19 vector<node>v[N];
20 vector<qnode>q[N];
21 bool vis[N];                //vis[i]表示点i是否被访问过
22 int res[N],root[N],dis[N];  //res记录答案
23 
24 int find(int x){
25     return root[x]==x?x:root[x]=find(root[x]);
26 }
27 
28 void lca(int u){
29     vis[u]=true;
30     for(int i=0;i<v[u].size();i++){
31         node t=v[u][i];
32         if(!vis[t.to]){
33             dis[t.to]=dis[u]+t.w;
34             lca(t.to);
35             root[t.to]=u;
36         }
37     }
38     for(int i=0;i<q[u].size();i++){
39         qnode t=q[u][i];
40         if(vis[t.to]){
41             res[t.id]=dis[u]+dis[t.to]-2*dis[find(t.to)];
42         }
43     }
44 }
45 
46 int main(){
47     int t;
48     scanf("%d",&t);
49     while(t--){
50         int n,m;
51         scanf("%d%d",&n,&m);
52         memset(vis,false,sizeof(vis));
53         memset(dis,0,sizeof(dis));
54         for(int i=1;i<=n;i++){
55             root[i]=i;
56             v[i].clear();
57         }
58         for(int i=1;i<=m;i++){
59             q[i].clear();
60         }
61         for(int i=1;i<=n-1;i++){
62             int a,b,c;
63             scanf("%d%d%d",&a,&b,&c);
64             v[a].push_back(node(b,c));
65             v[b].push_back(node(a,c));
66         }
67         for(int i=1;i<=m;i++){
68             int a,b;
69             scanf("%d%d",&a,&b);
70             q[a].push_back(qnode(b,i));
71             q[b].push_back(qnode(a,i));
72         }
73         lca(1);
77 for(int i=1;i<=m;i++){ 78 printf("%d\n",res[i]); 79 } 80 } 81 return 0; 82 }

倍增法(时间复杂度O(nlogn+Qlogn))

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<vector>
 5 #include<algorithm>
 6 using namespace std;
 7 const int N=1e5+5;
 8 
 9 struct node{
10     int to,w;
11     node(int to,int w):to(to),w(w){}
12 };
13 vector<node>v[N];
14 
15 int n,m;
16 int depth[N],fa[N][31],dis[N];
17 bool vis[N];
18 
19 
20 void init(){
21     memset(depth,0,sizeof(depth));
22     memset(fa,0,sizeof(fa));
23     memset(dis,0,sizeof(dis));
24     memset(vis,false,sizeof(vis));
25     for(int i=1;i<=n;i++) v[i].clear();
26 }
27 
28 void dfs(int u){
29     vis[u]=true;
30     for(int i=0;i<v[u].size();i++){
31         node t=v[u][i];
32         if(!vis[t.to]){
33             depth[t.to]=depth[u]+1;
34             fa[t.to][0]=u;
35             dis[t.to]=dis[u]+t.w;
36             dfs(t.to);
37         }
38     }
39 }
40 
41 //倍增,处理fa数组
42 void bz(){
43     for(int j=1;j<=30;j++){
44         for(int i=1;i<=n;i++){
45             fa[i][j]=fa[fa[i][j-1]][j-1];
46         }
47     }
48 }
49 
50 int lca(int x,int y){
51     //保证深度大的点为x
52     if(depth[x]<depth[y])
53         swap(x,y);
54     int dc=depth[x]-depth[y];
55     for(int i=0;i<30;i++){
56         if(1<<i&dc)                 //一个判断,模拟下就会清楚
57             x=fa[x][i];
58     }
59     if(x==y)    return x;           //如果深度一样,两个点相同,直接返回
60     for(int i=29;i>=0;i--){
61         if(fa[x][i]!=fa[y][i]){     //跳2^i不一样,就跳,否则不跳
62             x=fa[x][i];
63             y=fa[y][i];
64         }
65     }
66     x=fa[x][0];
67     return x;
68 }
69 
70 int main(){
71     int t;
72     scanf("%d",&t);
73     while(t--){
74         init();
75         scanf("%d%d",&n,&m);
76         for(int i=1;i<=n-1;i++){
77             int a,b,c;
78             scanf("%d%d%d",&a,&b,&c);
79             v[a].push_back(node(b,c));
80             v[b].push_back(node(a,c));
81         }
82         dfs(1);
83         bz();
84         for(int i=1;i<=m;i++){
85             int x,y;
86             scanf("%d%d",&x,&y);
87             printf("%d\n",dis[x]+dis[y]-2*dis[lca(x,y)]);
88         }
89     }
90     return 0;
91 }

 相关题目了解一下(转自这里):

hdu 2586 How far away ?

模板题,直接求LCA,可以在线,离线算法均可解,可以测试一下模板

 

poj 1986 Distance Queries

模板题,直接求LCA

 

hdu 2874 Connections between cities

模板题,不过不是树是森林,所以某些点不存在LCA,要做判断

 

zoj 3195 Design the city

任然算是模板题,上面的题要求两点间的最短距离,这里要求3点间的最短距离,其实就是两两之间求一次LCA并计算出距离,然后相加除以2即可

 

hdu 3078 Network

LCA + 修改点权值 + 排序:每个点有初始的权值,一边查询一边伴随着修改某些点的权值,查询是从a到b路径中第k大的权值是多少。不需要太多的技巧,修改操作就直接修改,查询操作先求LCA,然后从a走到b保存下所有的权值,排序,然后直接输出即可

 

poj 2763 Housewife Wind

LCA + 修改边权:一边查询两点间的距离,一边修改某些边权。对于修改了某些边的边权,就要从此开始遍历下面的子孙后代更改他们的dir值(点到根的距离)。也不需要太多技巧,直接按题意实现即可,不过时间比较糟糕,用线段树或树状数组可以对修改操作进行优化,时间提升很多

 

poj 3694 Network

连通分量 + LCA : 先缩点,再求LCA,并且不断更新,这题用了朴素方法来找LCA,并且在路径上做了一些操作

 

poj 3417 Network

LCA + Tree DP  :  在运行Tarjan处理完所有的LCA询问后,进行一次树DP,树DP不难,但是要想到用树DP并和这题结合还是有点难度

 

poj 3728 The merchant

LCA + 并查集的变形,优化:好题,难题,思维和代码实现都很有难度,需要很好地理解Tarjan算法中并查集的本质然后灵活变形,需要记录很多信息(有点dp的感觉)

 

hdu 3830 Checkers

LCA + 二分:好题,有一定思维难度。先建立出二叉树模型,然后将要查询的两个点调整到深度一致,然后二分LCA所在的深度,然后检验

posted @ 2018-03-24 16:08  Yeader  阅读(1049)  评论(0编辑  收藏  举报