最近公共祖先:LCA及其用倍增实现 +POJ1986
Q:为什么我在有些地方看到的是最小公共祖先?
A:最小公共祖先是LCA(Least Common Ancestor)的英文直译,最小公共祖先与最近公共祖先只是叫法不同。
Q:什么是最近公共祖先(LCA)?
A:最近公共祖先的概念是很好理解的。首先,你需要脑补出一棵树(它可以是二叉树,也可以是多叉树。)之后,请你再在你脑补出的树上任取两个点。每个点都可以到达树根,且到达的路径是唯一的,既然两个点都可以到达树根,那么根无疑是这两个点的公共祖先。然而,根却不一定是这两个点的最近公共祖先,相反,离根距离最远且在两条点到根路径上的点才是最近公共祖先(最近公共父节点)。
实现求LCA的方法有很多种,无论是离线还是在线,是TARJAN还是RMQ等等都可以实现。在此,安利一种PO主钟爱的方法:倍增,来实现LCA。
首先,你不可以认为倍增实现LCA和RMQ实现LCA是指的同一回事情,哪怕RMQ的完成是用了倍增思想。事实上,RMQ实现LCA的程序比较繁琐,并且需要你考虑到众多细节。而这些细节的调试在比赛有限的时间内无疑是要爆炸的。比如说,PO主就在某次D2T3跪在了RMQ实现LCA上QWQ。与RMQ相反,倍增实现的代码要比RMQ实现简单一些,并且好脑补,而且容易调试,相信大家一定可以弄明白倍增实现LCA的QWQ
在没有学习倍增写LCA之前,你是怎么样求LCA的呢?至少,我是老老实实地让这两个点一步一步往上移并找出它们的路径第一次交汇的地方。这种方法固然可行、好想,但它的效率实在不高。但是,我们完全可以通过提高“这两个点一步一步往上移”来提高效率。
所以,我们采用倍增的思路来预处理,分别记录这点的祖先,记录为anc[i][j]。即为第i个点往上2^j个祖先。比如说,当j=0时,2^j=1,anc[i][j]是第i个点的上一个节点,即它的父亲节点。
那么该如何预处理出anc数组呢?
int anc[][];
int fa[];
vector <int > tree[];
int deep[]; void dfs(int x)
{
anc[x][]=fa[x];
for (int i=;i<=;i++)
{
anc[x][i]=anc[anc[x][i-]][i-];//倍增思想的体现。不妨在纸上试着画一棵树,脑补一下QWQ
} for (int i=;i<tree[x].size();i++)
{
if (tree[x][i]!=fa[x])
{
int y=tree[x][i];
fa[y]=x;//记录父亲节点
deep[y]=deep[x]+;//记录深度
dfs(y);
}
}
}
通过从根节点开始的DFS,我们就预处理好了ANC数组。
下面,我们来考虑如何处理LCA查询。即每次给你两点X和Y,求出它们的LCA(X,Y)。在有了ANC数组之后,求出最近公共祖先就会变得很简单。
首先,让X,Y在同一深度上。在大多数情况下,查询给你的两个点X和Y它们的深度是不同的。但是,如果两点的深度相同,我们就可以实现两个点同时倍增比较何时祖先相同。所以,第一步是使X,Y中深度较深的点往上移动直到与另一个点深度相同。当然,点的移动也可以用倍增完成。
然后,当两点深度相同后,同时向上倍增两个点,当它们祖先刚好相同时,这个祖先就是它们的LCA。
如果你还是有一些不理解的话,不妨看LCA实现的代码QAQ
int lca(int x,int y)
{
if (deep[x]<deep[y]) _swap(x,y);//我们希望X是较深的点。 for (int i=;i>=;i--)//这个循环在完成第一步。
{
if (deep[y]<=deep[anc[x][i]]) //不可以丢掉“=“哦Q^Q
{
x=anc[x][i];
}
} if (x==y) return x;//如果Y是X的祖先,就可以直接返回结果了。 for (int i=;i>=;i--)
{
if (anc[x][i]!=anc[y][i]) //第二步。
{
x=anc[x][i];
y=anc[y][i];
}
} return anc[x][];//注意第二步IF语句的条件。
}
Q:为什么可以保证倍增就可以刚好到达那个我想要的点呢?
A:你不妨假设你现在点的位置到你想要的点的位置之间距离为D,那么D是整数,那么D一定可以用二进制表示,还记得ANC的第二维代表什么意思吗?二进制数D为1的地方就是我们需要往上翻的地方(比较抽象,适合画一画QAQ),为0的地方我们不动就好。
同时,LCA还可以用于求树上两点之间的距离。比如说POJ1986.
Input
* Line 2+M: A single integer, K. 1 <= K <= 10,000
* Lines 3+M..2+M+K: Each line corresponds to a distance query and contains the indices of two farms.
Output
Sample Input
7 6
1 6 13 E
6 3 9 E
3 5 7 S
4 1 3 N
2 4 20 W
4 7 2 S
3
1 6
1 4
2 6
Sample Output
13
3
36
Hint
这道题目可以直接忽视方向哦QAQ
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <set>
#include <vector>
#include <cstring>
using namespace std; int n,m,q;
int up[],de[],dp[][],fa[];
int pn[],pg[],pv[],st[];
int tot=; void init()
{
memset(up,,sizeof(up));
memset(de,,sizeof(de));
memset(dp,,sizeof(dp));
memset(pn,,sizeof(pn));
memset(pg,,sizeof(pg));
memset(pv,,sizeof(pv));
memset(fa,,sizeof(fa)); return ;
} void ins(int x,int y,int w)
{
pv[++tot]=y;
pg[tot]=w;
pn[tot]=st[x];
st[x]=tot; return;
} void dfs(int x)
{
dp[x][]=fa[x];
for(int i=;i<;i++)
{
dp[x][i]=dp[dp[x][i-]][i-];
} for (int i=st[x];i;i=pn[i])
{
int cur=pv[i];
if (cur==fa[x]) continue;
de[cur]=de[x]+;
up[cur]=up[x]+pg[i];
fa[cur]=x;
dfs(cur);
} return;
} int lca(int x,int y)
{
if (de[x]<de[y]) {
int t=x;
x=y;
y=t;
} for (int i=;i>=;i--)
{
if (de[dp[x][i]]>=de[y]) x=dp[x][i];
} if (x==y) return x; for (int i=;i>=;i--)
{
if (dp[x][i]!=dp[y][i])
{
x=dp[x][i];
y=dp[y][i];
}
} return dp[x][];
} int main()
{ while(~scanf("%d%d",&n,&m))
{
init();
int a,b,c;
for (int i=;i<=m;i++)
{
char s[];
scanf("%d%d%d%s",&a,&b,&c,s);
ins(a,b,c); ins(b,a,c);
} fa[]=;
de[]=up[]=;
dfs(); scanf("%d",&q); while(q--)
{
scanf("%d%d",&a,&b); printf("%d\n",up[a]+up[b]-*up[lca(a,b)]);//求树上距离。
}
} return ;
}
最近公共祖先:LCA及其用倍增实现 +POJ1986的更多相关文章
- 【lhyaaa】最近公共祖先LCA——倍增!!!
高级的算法——倍增!!! 根据LCA的定义,我们可以知道假如有两个节点x和y,则LCA(x,y)是 x 到根的路 径与 y 到根的路径的交汇点,同时也是 x 和 y 之间所有路径中深度最小的节 点,所 ...
- Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集)
Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集) Description sideman做好了回到Gliese 星球的硬件准备,但是sideman的导航系统还没有完全设计好.为 ...
- POJ 1470 Closest Common Ancestors(最近公共祖先 LCA)
POJ 1470 Closest Common Ancestors(最近公共祖先 LCA) Description Write a program that takes as input a root ...
- POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA)
POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA) Description A ...
- [模板] 最近公共祖先/lca
简介 最近公共祖先 \(lca(a,b)\) 指的是a到根的路径和b到n的路径的深度最大的公共点. 定理. 以 \(r\) 为根的树上的路径 \((a,b) = (r,a) + (r,b) - 2 * ...
- 最近公共祖先 LCA 倍增算法
树上倍增求LCA LCA指的是最近公共祖先(Least Common Ancestors),如下图所示: 4和5的LCA就是2 那怎么求呢?最粗暴的方法就是先dfs一次,处理出每个点的深度 ...
- luogu3379 【模板】最近公共祖先(LCA) 倍增法
题目大意:给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 整体步骤:1.使两个点深度相同:2.使两个点相同. 这两个步骤都可用倍增法进行优化.定义每个节点的Elder[i]为该节点的2^k( ...
- 最近公共祖先 LCA 倍增法
[简介] 解决LCA问题的倍增法是一种基于倍增思想的在线算法. [原理] 原理和同样是使用倍增思想的RMQ-ST 算法类似,比较简单,想清楚后很容易实现. 对于每个节点u , ancestors[u] ...
- lca最近公共祖先(st表/倍增)
大体思路 1.求出每个元素在树中的深度 2.用st表预处理的方法处理出f[i][j],f[i][j]表示元素i上方第2^j行对应的祖先是谁 3.将较深的点向上挪,直到两结点的深度相同 4.深度相同后, ...
随机推荐
- phpstorm 2016.1注册码
phper 享受生产PHP Web开发phpStorm.利用深代码理解,一流的编码的援助,并支持所有主要的工具和框架. 先看看 phpstorm 2016.1 带来那些新变化呢? 1,更好的PHP语言 ...
- SQL中对日期进行模糊查询的方法
在我们通过SQL语句对数据库中的数据进行查询时,难免会遇到针对datetime的查询,但是因为一般情况下,输入的时间条件为年月日,所以,这种情况下,我们就要进行一下模糊查询,首先,摒弃一种投机取巧的方 ...
- PreparedStatement可以有效地防止sql被注入
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import jav ...
- web登录与授权
web开发已经流行了很多年,登录与授权也基本有一套通用的流程,下面是我自己常用的登录与授权方式,欢迎大家讨论与吐槽. 概念: 登录是过程,授权是结果.登录只是为了获得页面的访问权限 or 操作权限 o ...
- xampp 命令行修改数据库密码
进入xampp 下 ./mysql -u root -p password 进入mysql控制台 UPDATE mysql.user SET Password=PASSWORD('password') ...
- Facebook和Google如何激发工程师的创造力
http://taiwen.lofter.com/post/664ff_ad8a15 今天终于“朝圣”了两个伟大的公司——Facebook和Google,对创造力和驱动力的来源有了更多的理解,尤其是对 ...
- 开发自定义View
当开发者打算派生自己的UI组件时,首先定义一个继承View基类的子类,然后重写View类的一个或多个方法,通常可以被用户重写的方法如下:构造器:重写构造器是定制View的最基本方法,当Java代码创建 ...
- VC++中调用cmd的集中方式
1. system方法: 原型: int __cdecl system(const char *); 例如: system("ipconfig"); 2. WinExec方法: 相 ...
- Java中4种权限的理解
1. 包访问权限 (1)包的理解:将一组相关的.有意义的类文件组织在一起(即相应的.java文件放在一个文件夹下)就构成了包或者类库.(每个类文件的开头都包含一个所属包的声明“package pac ...
- Windows下oracle打补丁步骤
1.Oracle官网下载对应的补丁文件(需要oracle支持账号才能下载) 2.设置ORACLE_HOME set oracle_home=F:\oracle\product\11.2.0\dbhom ...