• 最近公共祖先(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 ",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
    ",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所在的深度,然后检验

  • 相关阅读:
    Datagrip导入导出为一个sql文件详细说明 (mysql)
    Linux/Unix/Mac OS下的远程访问和文件共享方式
    批量杀掉多个pid文件中记录的pid进程, 并集成到shell脚本中
    把tomcat服务器配置为windows服务的方法
    idea导入java项目
    linux-umount挂载点无法卸载:device is busy(解决)
    elasticsearch插件大全
    分布式搜索elasticsearch配置文件详解
    centos fastdfs 多服务器 多硬盘 多组 配置详解
    redis 配置 linux
  • 原文地址:https://www.cnblogs.com/fu3638/p/8639429.html
Copyright © 2020-2023  润新知