最近公共祖先: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.深度相同后, ...
随机推荐
- d008: 求两数的整数商 和 商
内容: 求两数的整数商 和 商 ,商保留两位小数 输入说明: 一行 两个整数 输出说明: 一行,一个整数,一个实数(两位小数) 输入样例: 12 8 输出样例 : 1 1.50 #include ...
- 刷入临时recovery
@echo off @echo 手机进 Fastboot 模式 fastboot.exe boot recovery.img @echo. @echo 等待手机进入临时REC pause echo o ...
- Set的并集
public static void main(String[] args) { Set<Long> old = new HashSet<>(); for (int i = 0 ...
- Eclipse插件管理
Eclipse 的特色之一,就是它的插件功能.可以说, Eclipse 是一个插件的大集合,所有的模块都以插件的形式存在.那么,究竟什么是插件呢? 插件( plug-in ),即 Eclipse 的功 ...
- 如何把SKYPE的发送消息由enter改为ctrl+enter?
如果您的skype是tom-skype3.8正式版.您可以在skype面板中,选择"工具"-"选项"-"会话"-"会话设置&quo ...
- JavaScript……
退役了好伤心…… 这几天搞研究性学习写网页版贪吃蛇代码……太蛋疼了 要学javascript,就还要搞AJAX.JQuery.JSON…… 我感觉整个人都不好了
- Android圆角矩形的实现
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:Android=&quo ...
- Android中ListView通过BaseAdapter实现数据的绑定
1. public class ListFiles extends Activity { ListView Listview=null; protected void onCreate(Bundle ...
- js一些通用方法的封装
//封装StringBuilder function StringBuilder() { this._string_ = new Array(); } StringBuilder.prototype. ...
- css入门之css选择器
CSS选择器 css的选择器最常用的是class选择器,定义方式如下. <!DOCTYPE html> <html lang="en"> <head& ...