LCA(最近公共祖先)——Tarjan

什么是最近公共祖先?

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

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

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

 

常用来求LCA的算法有:Tarjan/DFS(离线),ST/倍增(在线)。

 

1,Tarjan

tarjan的算法复杂度为$O(n+q)$。

思路:每进入一个节点u的深搜,就把整个树的一部分看作以节点u为根节点的小树,再搜索其他的节点。每搜索完一个点后,如果该点和另一个已搜索完点为需要查询LCA的点,则这两点的LCA为另一个点的现在的祖先。

  1. 先建两个图,一个为树的各条边,另一个是需要查询最近公共祖先的两节点。
  2. 建好后,从根节点开始进行一遍深搜。
  3. 先把该节点u的father设为他自己(也就是只看大树的一部分,把那一部分看作是一棵树),搜索与此节点相连的所有点v,如果点v没被搜索过,则进入点v的深搜,深搜完后把点v的father设为点u。
  4. 深搜完一点u后,开始判断节点u与另一节点v是否满足求LCA的条件,满足则将结果存入数组中。
  5. 搜索完所有点,自动退出初始的第一个深搜,输出结果。

例.poj1470

传送:http://poj.org/problem?id=1470

题意:有n个点的树。m个询问,每个询问要求求出<u,v>的LCA。最终输出某个点作为询问中的LCA出现的次数。

分析:(题意描述有点不清喵喵喵???默认输入是父亲到儿子,然后手动记录入度,找到根节点。

 1 #include<cstdio>
 2 #include<iostream>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<string>
 6 #include<vector>
 7 using namespace std;
 8 const int maxn=910;
 9 typedef pair<int,int> pii;
10 vector<pii> q[maxn];
11 vector<int> mp[maxn];
12 int ans[maxn*maxn],num[maxn*maxn];
13 int f[maxn],vis[maxn],flag[maxn];
14 int find(int x){
15     if (f[x]==x) return x;
16     else return f[x]=find(f[x]);
17 }
18 void LCA(int root){
19     f[root]=root;
20     vis[root]=1;
21     for (int i=0;i<mp[root].size();i++){
22         int tmp=mp[root][i];
23         if (vis[tmp]) continue;
24         LCA(tmp);
25         f[tmp]=root;
26     } 
27     for (int i=0;i<q[root].size();i++){
28         pii tmp=q[root][i];
29         if (vis[tmp.first])
30             ans[tmp.second]=find(tmp.first);
31     }
32 }
33 int main(){
34     int n,m,x,y,kk;
35     while (~scanf("%d",&n)){
36         memset(flag,0,sizeof(flag));
37         for (int i=0;i<n;i++){
38             scanf("%d:(%d)",&x,&kk);
39             while (kk--){
40                 scanf("%d",&y);
41                 flag[y]=1;
42                 mp[x].push_back(y);
43                 //mp[y].push_back(x);
44             }
45         }
46         scanf("%d",&m); char ch;
47         for (int i=0;i<m;i++){
48             cin >> ch;
49             scanf("%d %d)",&x,&y);
50             q[x].push_back({y,i});
51             q[y].push_back({x,i});
52         }
53         for (int i=1;i<=n;i++) vis[i]=0;
54         int root;
55         for (int i=1;i<=n;i++) if (!flag[i]){root=i;break;}
56         LCA(root);
57         memset(num,0,sizeof(num));
58         for (int i=0;i<m;i++) num[ans[i]]++;
59         for (int i=1;i<=n;i++)
60             if (num[i]) printf("%d:%d\n",i,num[i]);
61     }
62     return 0;
63 } 
poj1470

练习:

1.hdu2586 How far away ?

传送:http://acm.hdu.edu.cn/showproblem.php?pid=2586

题意:求树上两点的最短距离。

分析:LCA。

树上最短路:$ans=dist[u]+dist[v]-2*dist[LCA(u,v)]$。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=4e4+10;
 4 typedef pair<int,int> pii;
 5 struct node{
 6     int to,val;
 7 };
 8 vector<node> mp[maxn];
 9 vector<pii> q[maxn];
10 int ans[210],f[maxn],vis[maxn],dis[maxn],flag[maxn];
11 int find(int x){
12     if (f[x]==x) return x;
13     else return f[x]=find(f[x]);
14 }
15 void LCA(int root){
16     f[root]=root;
17     vis[root]=1;
18     for (int i=0;i<mp[root].size();i++){
19         node tmp=mp[root][i];
20         if (vis[tmp.to]) continue;
21         dis[tmp.to]=dis[root]+tmp.val;
22         LCA(tmp.to);
23         f[tmp.to]=root;
24     }
25     for (int i=0;i<q[root].size();i++){
26         pii tmp=q[root][i];
27         if (vis[tmp.first]){
28             ans[tmp.second]=dis[root]+dis[tmp.first]-2*dis[find(tmp.first)];
29         } 
30     }
31 }
32 int main(){
33     int t,n,m,x,y,z; scanf("%d",&t);
34     while (t--){
35         scanf("%d%d",&n,&m);
36         for (int i=1;i<=n;i++) mp[i].clear(),flag[i]=0,q[i].clear(),dis[i]=0,vis[i]=0;
37         for (int i=0;i<n-1;i++){
38             scanf("%d%d%d",&x,&y,&z);
39             mp[x].push_back({y,z});
40             mp[y].push_back({x,z});
41             flag[y]=1;
42         } 
43         for (int i=0;i<m;i++){
44             scanf("%d%d",&x,&y);
45             q[x].push_back({y,i});
46             q[y].push_back({x,i});
47         }
48         int root;
49         for (int i=1;i<=n;i++) if (!flag[i]){root=i;break;}
50         LCA(root);
51         for (int i=0;i<m;i++) printf("%d\n",ans[i]);
52     }
53     return 0;
54 }
hdoj2586

 2.hdu2874 Connections between cities

传送:http://acm.hdu.edu.cn/showproblem.php?pid=2874

题意:n个点,m条边。问树上两点间最短路。

分析:图是森林。LCA。(卡内存,mle了若干发。

需要判断未经过的点作为新的一个树,求解答案。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int maxn=1e4+5;
 4 const int maxm=1e6+5;
 5 typedef pair<int,int> pii;
 6 struct node{
 7     int to,val,nxt;
 8 }mp[maxm*2];
 9 struct node2{
10     int to,id,nxt;
11 }q[maxm*2];
12 int head[maxn],q_head[maxn];
13 int ans[maxm],f[maxn],dis[maxn];
14 bool vis[maxn];
15 int tot,tot2;
16 void addedge(int x,int y,int z){
17     mp[tot].to=y;
18     mp[tot].val=z;
19     mp[tot].nxt=head[x];
20     head[x]=tot++;
21 }
22 void addquery(int x,int y,int id){
23     q[tot2].to=y;
24     q[tot2].id=id;
25     q[tot2].nxt=q_head[x];
26     q_head[x]=tot2++;
27 }
28 int find(int x){
29     if (f[x]==x) return x;
30     else return f[x]=find(f[x]);
31 }
32 void LCA(int root){
33     f[root]=root;
34     vis[root]=1;
35     for (int i=head[root];i!=-1;i=mp[i].nxt){
36         node tmp=mp[i];
37         if (vis[tmp.to]) continue;
38         dis[tmp.to]=dis[root]+tmp.val;
39         LCA(tmp.to);
40         f[tmp.to]=root;
41     }
42     for (int i=q_head[root];i!=-1;i=q[i].nxt){
43         node2 tmp=q[i];
44         if (vis[tmp.to]){
45             if (dis[tmp.to]!=-1) ans[tmp.id]=dis[root]+dis[tmp.to]-2*dis[find(tmp.to)];
46         }
47     }
48 }
49 int main(){
50     int n,m,c,x,y,z;
51     while (~scanf("%d%d%d",&n,&m,&c)){
52         //for (int i=1;i<=n;i++) head[i]=-1,q_head[i]=-1,flag[i]=false;
53         //for (int i=0;i<=c;i++) ans[i]=-1;
54         memset(head,-1,sizeof(head));
55         memset(q_head,-1,sizeof(q_head));
56         memset(ans,-1,sizeof(ans));
57         memset(vis,0,sizeof(vis));
58         tot=0;tot2=0;
59         for (int i=0;i<m;i++){
60             scanf("%d%d%d",&x,&y,&z);
61             addedge(x,y,z);
62             addedge(y,x,z);
63         }
64         for (int i=0;i<c;i++){
65             scanf("%d%d",&x,&y);
66             addquery(x,y,i);
67             addquery(y,x,i);
68         }
69         for (int i=1;i<=n;i++){
70             if (!vis[i]){
71                 //for (int j=1;j<=n;j++) vis[j]=0,dis[j]=0;  
72                 memset(dis,-1,sizeof(dis));
73                 dis[i]=0; 
74                 LCA(i);
75             }
76         }
77         for (int i=0;i<c;i++){
78             if (ans[i]==-1) printf("Not connected\n");
79             else printf("%d\n",ans[i]);
80         }
81     }
82     return 0;
83 }
hdoj2874

 

posted @ 2019-03-13 11:11  Changer-qyz  阅读(364)  评论(0编辑  收藏  举报