有关LCA的模板题    传送门

题目描述

如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

输入输出格式

输入格式:

第一行包含三个正整数N、M、S,分别表示树的结点个数、询问的个数和树根结点的序号。

接下来N-1行每行包含两个正整数x、y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树)。

接下来M行每行包含两个正整数a、b,表示询问a结点和b结点的最近公共祖先。

输出格式:

输出包含M行,每行包含一个正整数,依次为每一个询问的结果。

输入输出样例

输入样例#1:

5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
输出样例#1:

4
4
1
4
4

说明

时空限制:1000ms,128M

数据规模:

对于30%的数据:N<=10,M<=10

对于70%的数据:N<=10000,M<=10000

对于100%的数据:N<=500000,M<=500000

样例说明:

该树结构如下:

第一次询问:2、4的最近公共祖先,故为4。

第二次询问:3、2的最近公共祖先,故为4。

第三次询问:3、5的最近公共祖先,故为1。

第四次询问:1、2的最近公共祖先,故为4。

第五次询问:4、5的最近公共祖先,故为4。

故输出依次为4、4、1、4、4。

一道在大佬眼中水的不行的题目,然而对于我这样的小白来说还是很有难度,所以就让我们从0开始。

度娘的解释:

LCA(Least Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先。 ———来自百度百科

说真的看不懂也没什么,度娘这个东西纯属科普。

正常的开始:

首先,我们先来看一张图

这是一棵树,我相信是个人都能看出来,在图中我们可以很清楚的看出17号节点和8号节点的LCA就是3号节点,而9和7的LCA就是7;

大致知道了什么是LCA后,下面我们就来看看LCA怎么求 QWQ

  • 暴力算法

暴力这个东西是个好东西,但是dalao的暴力和你的暴力不是一个暴力,人家会加一些神仙优化,但你就不会。。。如果你还头铁的话,我们来看下复杂度。以17和18为例,如果要求LCA,我们会打暴力让他一个一个往上爬,在这两个点相遇时就停止,手动跑一下,就是

17号点:17-->14-->10-->7--3

18号点:18-->16-->12-->8-->5-->3

虽然最终结果是3没有错,但这样打你也许会听到旁边dalao“你不T谁T”的嘲讽,所以,为了营造良好的机房氛围我们在确定思路后,就要开始优化了,于是就有了那个几乎无人不知无人不晓的倍增求LCA

  • 倍增算法

倍增这个东西要是明白了就很简单,简单点说就是按照2的次方步来跳如2,4,8,16,32,64,128......只是我是从大往小跳,如果大的步数跳多了在试小一点的,有点像悔棋的感觉,以5为例,如果从小往大跳,5<1+2+4,所以结束后还要回溯才能得到5,但如果5=4+1,这样就很方便了,这也可以从二进制来理解,从高位往低位填数比从低位往高位填简单的多,这用代码实现也比较简单。

回到图中:17会直接往上跳4步到3,而18会跳4步后再跳1步到3(并非LCA真正的路径只是演示一下),比刚才的无脑暴力不知道快多少倍。

事实也却实如此此时的复杂度为O(nlogn),正常的题目都够用了,

算法实现

要实现也很简单,我们要记录每个点的深度deep,用parents[i][j]来记录i的2j级祖先,所用的大致变量如下

 const int maxn=1e6+;
struct node
{
int to;//连结到的边
int next;
}way[maxn<<];
int head[maxn];//邻接表存表的好伙伴
int parents[maxn][];//当前点的倍增祖先们,2的21次方足够了
int tot;
int deep[maxn];//深度

然后跑一个DFS来预处理一下

 int dfs(int x,int father)//x为当前节点,father为其父节点
{
deep[x]=deep[father]+;//当前点的深度为其父节点深度加1
parents[x][]=father;//当前点的2^0祖先(也就是上1级祖先)就是其父节点
for(int i=;(<<i)<=deep[x];i++)
{
parents[x][i]=parents[parents[x][i-]][i-];
//这里应该是整个预处理阶段中最有灵魂的部分了
//x的2^i级祖先就是x的2^(i-1)级祖先的2^(i-1)级的祖先 。
//2^i==2^(i-1)+2^(i-1),这个式子好像没什么可说的
}
for(int i=head[x];i;i=way[i].next)
{
int to=way[i].to;
if(to!=father)
dfs(to,x);
}
}

重点来了

接下来就是倍增了,为了方便,我们要先把两个点调到同一深度才统一开始跳

但是我们千万不可以直接就跳到LCA上,就像前面的图上,我们完全可以把4和8直接跳到1,但1只是公共祖先而不是LCA,然后我们可以跳到LCA的下一层,然后输出他们的共同的父节点这样就会防止误判了。

 int lca(int a,int b)//a,b为两个要查询的点
{
if(deep[a]>deep[b])//我时刻保证a的深度比b的小
{
swap(a,b); //如果反了就换一下
}
for(int i=;i>=;i--)
{
if(deep[a]<=deep[b]-(<<i))
b=parents[b][i];//将a和b跳的同一高度
}
if(a==b)//如果b在跳上来时和a一样了,那说明a就是a和b的LCA,直接返回就行了
return a;
for(int i=;i>=;i--)
{
if(parents[a][i]==parents[b][i])
continue;
else
{
a=parents[a][i];
b=parents[b][i];//将a和b一起往上跳
}
}
return parents[a][];//找出最后的答案
}

真正LCA的路径为:

17号节点: 17--->10--->7--->3

18号节点: 18--->16--->8--->5--->3

解释一下,18和17先跳到同一深度,再跳到LCA的下一层,17跳到7,18跳到5,随后的LCA就是5和7的共同父节点

优化

在预处理完后,为了跑的更快,可以加一个常数优化

     for(int i=;i<=n;i++)//预先算出log的值,在后来就可直接调用
{
lg[i]=lg[i-]+(<<lg[i-]==i);//一个很有名的公式,看不懂的可以百度一下推的过程
}

总结

LCA就这么多,主要还是要多练一练题目,不然就算告诉你是LCA,你都不会打,除了,开头的模板题,这题也算是半模板吧---->传送门

最后把完整的代码放一下

 #include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring> using namespace std; const int maxn=1e6+;
struct node
{
int to;//连结到的边
int next;
}way[maxn<<];
int head[maxn];//邻接表存表的好伙伴
int parents[maxn][];//当前点的倍增祖先们,2的21次方足够了
int tot;
int deep[maxn];//深度
int n,m,s; int add(int x,int y)//标准的领接表存边
{
way[++tot].next =head[x];
way[tot].to=y;
head[x]=tot;
}
void dfs(int x,int father)//x为当前节点,father为其父节点
{
deep[x]=deep[father]+;//当前点的深度为其父节点深度加1
parents[x][]=father;//当前点的2^0祖先(也就是上1级祖先)就是其父节点
for(int i=;(<<i)<=deep[x];i++)
{
parents[x][i]=parents[parents[x][i-]][i-];
//这里应该是整个预处理阶段中最有灵魂的部分了
//x的2^i级祖先就是x的2^(i-1)级祖先的2^(i-1)级的祖先 。
//2^i==2^(i-1)+2^(i-1),这个式子好像没什么可说的
}
for(int i=head[x];i;i=way[i].next)
{
int to=way[i].to;
if(to!=father)
dfs(to,x);
}
} int lca(int a,int b)//a,b为两个要查询的点
{
if(deep[a]>deep[b])//我时刻保证a的深度比b的小
{
swap(a,b); //如果反了就换一下
}
for(int i=;i>=;i--)
{
if(deep[a]<=deep[b]-(<<i))
b=parents[b][i];//将a和b跳的同一高度
}
if(a==b)//如果b在跳上来时和a一样了,那说明a就是a和b的LCA,直接返回就行了
return a;
for(int i=;i>=;i--)
{
if(parents[a][i]==parents[b][i])
continue;
else
{
a=parents[a][i];
b=parents[b][i];//将a和b一起往上跳
}
}
return parents[a][];//找出最后的答案
} int main()
{
scanf("%d %d %d",&n,&m,&s);//亲生经验告诉我们cin只能用于调试之类的
//cin>>n>>m>>s;
for(int i=;i<=n-;i++)
{
int a,b;
scanf("%d %d",&a,&b);
//cin>>a>>b;
add(a,b);//因为是树,所以是双向边
add(b,a);
}
dfs(s,);
for(int i=;i<=m;i++)
{
int a,b;
scanf("%d %d",&a,&b);
//cin>>a>>b;
printf("%d\n",lca(a,b));
//cout<<lca(a,b)<<endl;
}
return ;
}

数据结构--树链剖分准备之LCA的更多相关文章

  1. 树链剖分 (求LCA,第K祖先,轻重链剖分、长链剖分)

      2020/4/30   15:55 树链剖分是一种十分实用的树的方法,用来处理LCA等祖先问题,以及对一棵树上的节点进行批量修改.权值和查询等有奇效. So, what is 树链剖分? 可以简单 ...

  2. 树链剖分(附带LCA和换根)——基于dfs序的树上优化

    .... 有点懒: 需要先理解几个概念: 1. LCA 2. 线段树(熟练,要不代码能调一天) 3. 图论的基本知识(dfs序的性质) 这大概就好了: 定义: 1.重儿子:一个点所连点树size最大的 ...

  3. 用树链剖分来写LCA

    当两个点在一条链上,它们的LCA就是深度较小的那个点. 于是这种树链剖分写LCA的思想就是把要求的两个点想办法靠到一条链上. 而且要靠到尽量更优的一条链上(重链). 做法: 预处理出每棵树上的重链(s ...

  4. BZOJ 3083: 遥远的国度 [树链剖分 DFS序 LCA]

    3083: 遥远的国度 Time Limit: 10 Sec  Memory Limit: 1280 MBSubmit: 3127  Solved: 795[Submit][Status][Discu ...

  5. 数据结构(树链剖分):COGS 2109. [NOIP2015] 运输计划

    2109. [NOIP2015] 运输计划 ★★★   输入文件:transport.in   输出文件:transport.out   简单对比时间限制:1 s   内存限制:256 MB [题目描 ...

  6. 数据结构(树链剖分):BZOJ 4034: [HAOI2015]T2

    Description 有一棵点数为 N 的树,以点 1 为根,且树点有边权.然后有 M 个 操作,分为三种: 操作 1 :把某个节点 x 的点权增加 a . 操作 2 :把某个节点 x 为根的子树中 ...

  7. 数据结构(树链剖分,堆):HNOI 2016 network

    2215. [HNOI2016]网络 ★★★☆   输入文件:network_tenderRun.in   输出文件:network_tenderRun.out   简单对比时间限制:2 s   内存 ...

  8. P3379 【模板】最近公共祖先(LCA)(树链剖分)版

    #include <bits/stdc++.h> #define read read() #define up(i,l,r) for(register int i = (l);i < ...

  9. LCA树链剖分

    LCA(Lowest Common Ancestor 最近公共祖先)定义如下:在一棵树中两个节点的LCA为这两个节点所有的公共祖先中深度最大的节点. 比如这棵树 结点5和6的LCA是2,12和7的LC ...

随机推荐

  1. 一致性哈希(PHP核心技术与最佳实践)

    <?php /** * 分布式缓存部署方案 * 当有1台cache服务器不能满足我们的需求,我们需要布置多台来做分布式服务器,但是 * 有个问题,怎么确定一个数据应该保存到哪台服务器上呢? * ...

  2. SpringBoot系列:Spring Boot使用模板引擎JSP

    一.Java模板引擎 模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档. 在jav ...

  3. 开发一个带UI的库(asp.net core 3.0)

    在GitHub上有个项目,本来是作为自己研究学习.net core的Demo,没想到很多同学在看,还给了很多星,所以觉得应该升成3.0,整理一下,写成博分享给学习.net core的同学们. 项目名称 ...

  4. 8.css背景

    ①Background-attachment背景图像是固定还是随着页面滚动.Scroll默认,fixed固定. ②Background-color背景颜色. ③Background-image把图像设 ...

  5. Java中冒泡排序法的代码实现方法之一

    主要运用双层for循环嵌套,进行冒泡排序 public class BubbleSortTest { public static void main(String[] args) { int[] ar ...

  6. mac上git安装与github基本使用

    目录 安装git 创建ssh key.配置git 提交本地项目到GitHub 一.安装Git MAC安装Git 首先查看电脑是否安装Git,终端输入: git 1.通过homebrew安装Git 1. ...

  7. X-扫描线算法

    多边形的扫描转换 多边形有两种重要的表示方法:顶点表示和点阵表示 顶点表示是用多边形的顶点序列来表示多边形.这种表示直观.几何意义强.占内存少,易于进行几何变换. 但由于它没有明确指出哪些象素在多边形 ...

  8. 不该背的锅也要背,Gitee.com被停止域名解析

    1.Gitee.com被停止域名解析 今天下午发现码云打不开了,打开是这样的 350万的男性交友平台说挂就挂,简直惨无人道!目前已有超过 350 万的开发者选择码云,不为啥,,就冲这个私有.免费这两个 ...

  9. java架构之路-(Redis专题)Redis的主从、哨兵和集群

    我们使用的redis,单机的绝对做不到高可用的,万一单机的redis宕机了,就没有备用的了,我们可以采用集群的方式来保证我们的高可用操作. 主从架构 大致就是这样的,一个主节点,两个从节点(一般两个就 ...

  10. 实用---GUI的搭建,windowbuilder的使用

    在进行GUI的搭建过程中,相信很多人对于一个图标的设置感觉写起来很麻烦,需要不断的添加,而在java中有一个windowbuilder窗口可以很好的帮助我们进行GUI的搭建 1.进入eclipse的页 ...