最近公共祖先: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.深度相同后, ...
随机推荐
- XML解析方式与解析工具
DOM解析原理: 1)JAXP (oracle-Sun公司官方) 2)JDOM工具(非官方) 3)Dom4J工具(非官方) 三大框架(默认读取xml的工具就是Dom4j) ....... SAX解析原 ...
- Xcode-Xcode 7.3 解决不能自动联想问题-备
一.问题: 升级Xcode 7.3 之后发现导入头文件之后,没法自动联想. 二. 解决办法: 打开Xcode --> Target --> BuildSettings --> App ...
- swift和 oc 混编2-备
在Swift语言出现之前,开发iOS或OS X应用主要使用Objective-C语言,此外还可以使用C和C++语言,但是UI部分只能使用Objective-C语言. 选择语言 Swift语言出现后,苹 ...
- 如何使用Reaver-PJ-Wi-Fi网络的WPA密码
在正式开始之前,我还是要不厌其烦强调一下:知识就是力量,但是拥有力量不代表着可以为所欲为.触犯法律.同样,骑白马的不一定是王子,会开锁的也不一定是小偷.本文只是关于某些技术的实验与验证,只适用于学习. ...
- yiic模块module使用
模块是一个独立的软件单元,它包含 模型, 视图, 控制器 和其他支持的组件. 在许多方面上,模块看起来像一个 应用.主要的区别就是模块不能单独部署,它必须存在于一个应用里. 用户可以像他们访问普通应用 ...
- Android 查看是否有存储卡插入
String status=Environment.getExternalStorageState(); 2 3 if ( status.equals ( Enviroment.MEDIA_MOU ...
- git 错误
1 执行 Git add somefile 的时候,出现 如下 错误: If no other git process is currently running, this probably m ...
- 我为什么要再给lua写一个json模块
最近要给自己编写的服务器加上json解析模块.根据我当前的项目,可以预测服务器中使用json的地方: 通信.由于与客户端通信使用google protocolbuffer,仅在与SDK通信中使用jso ...
- TTTAttributedLabel使用介绍(转)
TTTAttributedLabel 库地址 https://github.com/TTTAttributedLabel/TTTAttributedLabel 可以实现电话 地址 链接自动查找显示 ...
- C语言--对数组地址的解析
在C编程中,我们进程会用到数组,这看起来很简单,因为,数组就是存储相同类型元素的集合嘛,不过,当你还没考虑到数组的地址问题时,一切都是简单的,如果你接触了数组中的地址概念,也许你会改变你的想法. 下面 ...