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.

Distance Queries
Farmer John's cows refused to run in his marathon since he chose a path much too long for their leisurely lifestyle. He therefore wants to find a path of a more reasonable length. The input to this problem consists of the same input as in "Navigation Nightmare",followed by a line containing a single integer K, followed by K "distance queries". Each distance query is a line of input containing two integers, giving the numbers of two farms between which FJ is interested in computing distance (measured in the length of the roads along the path between the two farms). Please answer FJ's distance queries as quickly as possible!
 

Input

* Lines 1..1+M: Same format as "Navigation Nightmare"

* 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

* Lines 1..K: For each distance query, output on a single line an integer giving the appropriate distance.

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

Farms 2 and 6 are 20+3+13=36 apart

这道题目可以直接忽视方向哦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的更多相关文章

  1. 【lhyaaa】最近公共祖先LCA——倍增!!!

    高级的算法——倍增!!! 根据LCA的定义,我们可以知道假如有两个节点x和y,则LCA(x,y)是 x 到根的路 径与 y 到根的路径的交汇点,同时也是 x 和 y 之间所有路径中深度最小的节 点,所 ...

  2. Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集)

    Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集) Description sideman做好了回到Gliese 星球的硬件准备,但是sideman的导航系统还没有完全设计好.为 ...

  3. POJ 1470 Closest Common Ancestors(最近公共祖先 LCA)

    POJ 1470 Closest Common Ancestors(最近公共祖先 LCA) Description Write a program that takes as input a root ...

  4. POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA)

    POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA) Description A ...

  5. [模板] 最近公共祖先/lca

    简介 最近公共祖先 \(lca(a,b)\) 指的是a到根的路径和b到n的路径的深度最大的公共点. 定理. 以 \(r\) 为根的树上的路径 \((a,b) = (r,a) + (r,b) - 2 * ...

  6. 最近公共祖先 LCA 倍增算法

          树上倍增求LCA LCA指的是最近公共祖先(Least Common Ancestors),如下图所示: 4和5的LCA就是2 那怎么求呢?最粗暴的方法就是先dfs一次,处理出每个点的深度 ...

  7. luogu3379 【模板】最近公共祖先(LCA) 倍增法

    题目大意:给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 整体步骤:1.使两个点深度相同:2.使两个点相同. 这两个步骤都可用倍增法进行优化.定义每个节点的Elder[i]为该节点的2^k( ...

  8. 最近公共祖先 LCA 倍增法

    [简介] 解决LCA问题的倍增法是一种基于倍增思想的在线算法. [原理] 原理和同样是使用倍增思想的RMQ-ST 算法类似,比较简单,想清楚后很容易实现. 对于每个节点u , ancestors[u] ...

  9. lca最近公共祖先(st表/倍增)

    大体思路 1.求出每个元素在树中的深度 2.用st表预处理的方法处理出f[i][j],f[i][j]表示元素i上方第2^j行对应的祖先是谁 3.将较深的点向上挪,直到两结点的深度相同 4.深度相同后, ...

随机推荐

  1. PHPCMS 标签与解析小记_Jason

    Content模块下的标签解析:phpcms\modules\content\classes\content_tag.class.php 推荐位:public function position

  2. 数据库和Doctrine(转载自http://www.111cn.net/phper/332/85987.htm)

    对于任何应用程序来说最为普遍最具挑战性的任务,就是从数据库中 读取和持久化数据信息.尽管symfony完整的框架没有默认集成ORM,但是symfony标准版,集成了很多程序,还自带集成了Doctrin ...

  3. Apache Rewrite 拟静态配置54

    mod_rewrite 规则的使用 RewriteEngine on RewriteCond %{HTTP_HOST} !^www.php100.com  [NC]  RewriteRule   ^/ ...

  4. PHP获取文件扩展名的多种方法

    PHP获取文件扩展名的N种方法. 第1种方法: function get_extension($file) { substr(strrchr($file, '.'), 1): } 第2种方法: fun ...

  5. jquery keyup 在IOS设备上输入中文时不触发

    今天做一个异步查询功能的时候发现在IOS设备上查询中文时keyup没有触发,在其他设备上时可以的,后来在stackoverflow上找到下面这种解决方法,贴出来算是抛砖引玉了. $h_input.on ...

  6. linux常用命令(5)rmdir命令

    rmdir是常用的命令,该命令的功能是删除空目录,一个目录被删除之前必须是空的.(注意,rm - r dir命令可代替rmdir,但是有很大危险性.)删除某目录时也必须具有对父目录的写权限.1.命令格 ...

  7. PHP 中的超全局变量

    (1)$_GET[].一个包含所有PHP 从客户端浏览器接收的GET变量的数组. (2)$_POST[].一个包含所有PHP 从客户端浏览器接收的POST变量的数组. (3)$_COOKIE[].一个 ...

  8. Qt之模型/视图(自定义按钮)(重绘QStyleOptionButton)

    http://blog.csdn.net/liang19890820/article/details/50974059#comments

  9. NERDTree这个插件的用法简介

    事情是这样子的,想做做李治军老师班的操作系统实验,但是Linux上的gedit太简陋了(这个简陋程度堪比Windows环境下的"记事本"),被杨世祺大神嘲笑了.我心想既然在linu ...

  10. 形形色色的软件生命周期模型(4)——MSF、实用型

    摘要: 读大学时,我们曾经学习过不少软件生命周期模型,当时还不是很懂软件开发,你可能会觉得这些东西很新奇.在实际工作中,你会发现这些模型其实很难应用,与此同时你会接触到RUP.MSF等权威软件公司的生 ...