• 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
    ",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
    ",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
    ");
    79             else printf("%d
    ",ans[i]);
    80         }
    81     }
    82     return 0;
    83 }
    hdoj2874
  • 相关阅读:
    Java 回调函数的理解
    Java对象初始化
    Hibernate中get方法和load方法的区别
    Java 如何判断导入表格某列是否有重复数据
    Java学习之Java的单例模式
    Java中怎么设置文件权限
    div居中问题
    JSON
    js
    ajax
  • 原文地址:https://www.cnblogs.com/changer-qyz/p/10484538.html
Copyright © 2020-2023  润新知