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 V9 简单的二次开发

    更多二次开发技巧,查看phpcms系统帮助 ,前台模板解析后的缓存 caches\caches_template\default 前台控制类index.php,前台标签类*_tag.class.php ...

  2. Python自动化运维之6、函数装饰器

    装饰器: 装饰器可以使函数执行前和执行后分别执行其他的附加功能,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator),装饰器的功能非常强大.装饰器一般接受一个函数对象作为参数, ...

  3. C语言+ODBC+SQL 操作(向SQL里面添加数据)

    为了节省时间,我就引用上一节的数据库的表和C语言的结构体数组,在结构体数组中添加数据,清空数据库数据. 第一步查询:SQLBindParameter函数的用法. SQLRETURN SQLBindPa ...

  4. 使用 Nuget打包类库

    使用 Nuget打包类库 NuGet是个开源项目,项目包括 NuGet VS插件/NuGet Explorer/NuGetServer/NuGet命令行等项目,.NET Core项目完全使用Nuget ...

  5. angularJs项目实战!04:angularjs的性能问题

    上一篇文章中我花了很多口舌去介绍angularjs是一个中型框架,面对大型应用时少不了第三方类库的配合.而我的核心议题是:如何以angularjs的思路使用其他类库,这里jquery是最好的例子了,谁 ...

  6. C++ 单向链表反转

    单向链表反转,一道常见的面试题,动手实现下. #include "stdafx.h" #include <stdlib.h> struct Node{ int data ...

  7. Android 将从网络获取的数据缓存到私有文件

    1:activity_main.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/androi ...

  8. java疯狂演义----简单java IDE工具

    file:commons package org.crazyit.editor.commons; import org.crazyit.editor.EditorFrame; import org.c ...

  9. js中()()问题

    var aa=function(){}(); var bb=(function(){})(); 今天被问到这个问题,这段js有撒区别. 总结一下,两个函数都是立即执行的意思.但是不同之处是执行的顺序, ...

  10. sdl2.0示例

    // gcc -o testDrone2_video testDrone2_video.c -lavcodec -lavformat -lswscale -lSDL2// g++ -o testDro ...