[模板]LCA的倍增求法解析
题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入输出格式
输入格式:
第一行包含三个正整数N、M、S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来N-1行每行包含两个正整数x、y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树)。
接下来M行每行包含两个正整数a、b,表示询问a结点和b结点的最近公共祖先。
输出格式:
输出包含M行,每行包含一个正整数,依次为每一个询问的结果。
输入输出样例
5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
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。
Solution:
题目来源:Luogu P3379
我们来讲讲LCA的倍增求法,来自于LS学长神犇的教授,加上我自身的理解,可能对各位新人的帮助会更有理解作用,所以我决定分享一下。(蒟蒻一本正经)
LCA——最近公共祖先。最朴素的算法无疑是从两个点一个个往上走,出现的第一个两个点都走过的点即为两点的LCA。这个无需多加解释。
然而这样时间复杂度非常高。尤其是当树退化成一条链的时候,每次查询的复杂度将会飙到O(N)。
为此,倍增的作用就是将两点上升所需的复杂度减低。而原理就是,把两点往上跳,跳到LCA.-.
流程概括为:将深度不同的两点跳到同一层(深一点的跳到浅一点的一层),然后再将两点向上跳到LCA的下面一层,最后再向上跳一层。(后面一部分下面会解释,为什么不能直接跳到LCA。)
快速跳的方法,每次向上跳的层数都为2^i层。(i为非负整数),相当于把层数差转成2进制数,一位一位往上跳,使层数差快速减小。这样,每次查询复杂度最高就只会为log2(N)。
实现方法:
我们首先需要两个数组。f[i][j]代表从i点向上跳2^j层后所到达的点,dep[i]代表这棵树中点i的深度。
dep数组可以在从根深度遍历整棵树的时候求得。
f数组利用了递推的思想。递推式为:f[i][j]=f[f[i][j-1]][j-1]。初始化f[i][0]也可以在遍历整棵树的时候求得。
之后,我们处理LCA时,按照上面的流程。我们先将较深的点一次次往上逼近较浅的点,直至他们层数相同。(相当于把层数差的二进制一位一位减去,直至变成0)
然后,两点再同时向上逼近。i从最高位开始枚举,假设两点分别为x,y,那么能向上跳的判断式为:if f[x][i]!=f[y][i] then x=f[x][i],y=f[y][i]。翻译成人话就是如果两点向上跳了2^i层以后不到同一个点就接着往上跳。为什么这样?因为如果往上跳了2^i层,即使到了同一个点,它不一定是两点的LCA。(为什么?)
这样做,最终就会到达LCA的下面一层。随后,我们再将两点向上跳一层。LCA求得。
为什么最终会到达LCA的下面一层?
我们假设从a,b点开始,往上跳2^j层,跳到同一点。不跳。往上跳2^(j-1)层,不跳到同一点,往上跳,分别到了A',B'。显然,这种情况是一定会存在的。
那么,从A',B’再往上跳到原来那个决定不跳的点,显然要跳2^(j-1)层。那么,那个点有可能是LCA,也有可能不是,对吧?所以,从A',B'往上跳到LCA所需的层数,是≤2^(j-1)的。
所以,从A',B'跳到LCA的下面一层X的所需层数,会是<2^(j-1)的。换句话来说,A',B'到X的层数变成了一个j-2位的二进制数(可能会有前导零,也就是还可能会跳到点数相同的地方)。而此时,刚好枚举到j-2位。那么,前导零不减,再这么减下去,你发现,这个层数差最终会变成0,而你最终也会到达第X层。(为什么?)
如果不懂,也可以先放着。你只需要知道这么做可以到LCA的下面一层就OK了orz
请最好自己手画一棵树模拟一下整个算法的过程。
蒟蒻解释难免有错误,如果出现错误,请回复我,我将感激不尽orz
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=,OP=log2()+;
int dpt[N][];
int dep[N]; int gi(){
int x=;
char ch=getchar();
while(ch<''||ch>'')ch=getchar();
while(ch>=''&&ch<='')x=x*+ch-'',ch=getchar();
return x;
} int h[N],to[N*],nexp[N*],p=;
inline void ins(int a,int b){
nexp[p]=h[a],h[a]=p,to[p]=b,p++;
} void dfs(int p,int x){
dep[x]=p;
for(int u=h[x];u;u=nexp[u]){
if(!dep[to[u]]){
dpt[to[u]][]=x;
dfs(p+,to[u]);
}
}
} int LCA(int a,int b){
if(dep[a]<dep[b])swap(a,b);
for(int j=OP;j>=;j--)
if(dep[a]-(<<j)>=dep[b])a=dpt[a][j];
if(a!=b){
for(int j=OP;j>=;j--)
if(dpt[a][j]!=dpt[b][j])a=dpt[a][j],b=dpt[b][j];
a=dpt[a][];
}
return a;
} int main(){
int n,q,r;
cin>>n>>q>>r;
int a,b;
for(int i=;i<n-;i++){
a=gi(),b=gi();
ins(a,b),ins(b,a);
}
dfs(,r);
for(int j=;j<=OP;j++)
for(int i=;i<=n;i++)
dpt[i][j]=dpt[dpt[i][j-]][j-];
for(int i=;i<q;i++){
a=gi(),b=gi();
printf("%d\n",LCA(a,b));
}
return ;
}
为什么与X层数差最终会变成0?
假设你已经会写这个算法了。
首先我们证明,前导零不会被减去。假设与X层的层数差为x',而你正准备往上跳y层。由于LCA的层数是X+1,而LCA往上的点它都不会跳,对吧?(反而,如果LCA往下的点,也就是层数<=X,也就是y<x'+1,它都会往上跳)所以如果y>=x'+1,那么就绝对不会往上跳。
显然,当x'的该位为0,且属于前导零,那么只需证明x'+1<=y。而这个非常易证(假设y为10000,而x'满足条件的最大值为01111)。所以保证,前导零是不会减去的。
接着我们证明,一旦枚举到了x'的第一个为1的位数,这个1绝对会被减去。按照同样的方法,假设y为10000,而x'满足条件的最小值为10000,所以y<x'+1.
两点合在一起,前导零不会减去,枚举到一个1位就减去,最终这个层数差就会变成0.证毕。
[模板]LCA的倍增求法解析的更多相关文章
- LCA树上倍增求法
1.LCA LCA就是最近公共祖先(Least common ancestor),x,y的LCA记为z=LCA(x,y),满足z是x,y的公共祖先中深度最大的那一个(即离他们最近的那一个)qwq 2. ...
- 关于LCA的倍增解法的笔记
emmmmm近日刚刚学习了LCA的倍增做法,写一篇BLOG来加强一下印象w 首先 何为LCA? LCA“光辉”是印度斯坦航空公司(HAL)为满足印度空军需要研制的单座单发轻型全天候超音速战斗攻击机,主 ...
- 模板类 error LNK2019: 无法解析的外部符号
如果将类模板的声明和实现写在两个独立的文件中,在构建时会出现"error LNK2019: 无法解析的外部符号 "的错误. 解决方法有: 第一种方法,就是把类模板中成员函数的声明和 ...
- 在VS中使用类模板出现出现LNK2019: 无法解析的外部符号错误。
在VS中使用类模板出现出现LNK2019: 无法解析的外部符号错误,应在一个.h文件中完成方法的声明与实现,不要将实现放在cpp文件里,VS貌似不支持类模板分离
- LCA的倍增算法
LCA,即树上两点之间的公共祖先,求这样一个公共祖先有很多种方法: 暴力向上:O(n) 每次将深度大的点往上移动,直至二者相遇 树剖:O(logn) 在O(2n)预处理重链之后,每次就将深度大的沿重链 ...
- Django框架04 /模板相关、别名/反向解析/路由分发
Django框架04 /模板相关.别名/反向解析/路由分发 目录 Django框架04 /模板相关.别名/反向解析/路由分发 1. 语法 2. 变量/万能的点 3 . 过滤器 4. 标签Tags 5. ...
- dede织梦会员模板调用template下模板head.htm方法及解析变量
1.找到dedecms会员中心的的目录 member ,然后在目录下用编辑器打开config.php 加入对dede模板解释函数如下: //php脚本开始 //引入arc.partview.cla ...
- 【原创】洛谷 LUOGU P3379 【模板】最近公共祖先(LCA) -> 倍增
P3379 [模板]最近公共祖先(LCA) 题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询 ...
- LCA算法倍增算法(洛谷3379模板题)
倍增(爬树)算法,刚刚学习的算法.对每一个点的父节点,就记录他的2k的父亲. 题目为http://www.luogu.org/problem/show?pid=3379 第一步先记录每一个节点的深度用 ...
随机推荐
- Markdown - 让网络书写变得简单
概述 宗旨 Markdown 的目标是实现「易读易写」. 可读性,无论如何,都是最重要的.一份使用 Markdown 格式撰写的文件应该可以直接以纯文本发布,并且看起来不会像是由许多标签或是格式指令所 ...
- laravel中单独获取一个错误信息的方法
获取单独的错误信息的方法
- Spring实例化bean的几种方式
一,通过constructor实例化bean Spring可以实例化各种类型的类,不要求必须是JavaBean类型的类.在XML中配置类如下: <bean id="exampleBea ...
- Easyui datagrid自定义排序
做项目遇到个关于排序问题,想着在前端排序,正好Easyui有这个功能,所以就拿来用了一下,因为跟官网的Demo不太一样,所以总结一下: 首先这一列是要排序的列(当然,在生产环境,这一列是隐藏的,在开发 ...
- 《Effective C++》第2章 构造/析构/赋值运算(1)-读书笔记
章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...
- 原生js重写《锋利的JS》之 轮播效果
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- location.host 与 location.hostname 的区别
JavaScript 中,大多数情况下,我们不会发现 location.host 与 location.hostname 的区别,因为大多数情况下,我们的网页用的是 80 端口. 他们的区别: loc ...
- RxJava 1.x 笔记:组合型操作符
最近去检查眼睛,发现度数又涨了,唉,各位猿多注意保护自己的眼睛吧! 前面学了 RxJava 的三种关键操作符: 创建型操作符 过滤型操作符 变换型操作符 读完本文你将了解第四种(组合型操作符): 组合 ...
- Tensorflow 运行警告提示 Your CPU supports instructions that this TensorFlow binary was not compiled to use
由于现在神经网络这个东西比较火,准确的说是深度学习这个东西比较火,我们实验室准备靠这个东西发几个CCF A类的文章,虽然我不太懂这东西,兴趣也一般都是毕竟要跟随主流的,于是今天安装起了 Tensorf ...
- js 字符串和数组注意点
var a="foo"; var b=[ "f","o","o"]; a[1]="o"; b[1]= ...