[模板]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 第一步先记录每一个节点的深度用 ...
随机推荐
- 201621123014《Java程序设计》第五周学习总结
1. 本周学习总结 1.1 写出你认为本周学习中比较重要的知识点关键词 答:接口.interface.implements.Comparable.Comparator 1.2 尝试使用思维导图将这些关 ...
- css 中相对定位和绝对定位
1. css中定位机制有三种: 标准文档流, 浮动, 绝对定位 2. 绝对定位就属于第三种定位, 用到position属性, 下面就是具体设置 相对定位: 相对于自身原有位置(就是普通流的时候)进行偏 ...
- jfc在jsp页面画图,不将图片存在服务器端,只存入会话session(可用)
jfc在jsp页面画图,不将图片存在服务器端,只存入会话session.其中主要用到jfc的一个servlet类. <%@ page contentType="text/html;ch ...
- [Linux] 输出文件的指定行
1.获取第k行(以k=10为例) 要注意的是,如果文件包含内容不足10行,应该不输出. # Read from the file file.txt and output the tenth line ...
- Python3 字符串操作
截掉指定字符串 # 截掉指定字符串 string.strip("what you want to delete") #截掉字符串左边的空格 string.lstrip() #截掉字 ...
- iOS调用第三方API/Framework
前言 老板不止一次地说过:这个世纪靠个人的能力去完成一件事情肯定是不够的.无论什么方面我们都可以找到许许多多的事例表明合作共赢的重要性,例如Linux的发展.建筑事务所的发展.乃至科学技术的发展等等. ...
- socket创建UDP服务端和客户端
UDP服务端代码示例: from socket import * #1.创建数据报套接字 sockfd = socket(AF_INET, SOCK_DGRAM) #2.绑定服务端地 sockfd.b ...
- MySQL 实用技巧
概述: MySQL有许多实用的技巧,利用这些技巧能提高工作的效率,减少一些不必要的麻烦.以下是几个我在MySQL日常维护从常用的技巧. 一.prompt 命令 功能:设置mysql客户端提示符 说明: ...
- sync-settings(vscode)
vscode插件以及设置 sync-download e45c6db33cd91d661e0cc545efb6817c
- BZOJ1412 ZJOI2009 狼和羊的故事 【网络流-最小割】
BZOJ1412 ZJOI2009 狼和羊的故事 Description “狼爱上羊啊爱的疯狂,谁让他们真爱了一场:狼爱上羊啊并不荒唐,他们说有爱就有方向......” Orez听到这首歌,心想:狼和 ...