这几天,提高B组总是有求LCA的题。由于我是蒟蒻,所以老是做不出来,直接上暴力。现在才弄懂。

没耐心看前面部分的大神门可以直接看后面。

ST(RMQ)算法(在线)求LCA


LCA是什么?

在一棵树上,两个节点的最近公共祖先就是LCA。

求LCA有什么用?

我见到最多的是,在一些题目中,我们需要找出树上两个点之间的路径,其中就要借助LCA,作为一个中转点。

举个例子:

我们要找出两个红色的点之间的路径。



黄色的这条路就是我们要求的。



怎么找?

暴力方法1

BFS或DFS遍历一遍。时间复杂度显然是O(N)的。

但我们要记住,这不是图,而是一棵树!

这是一棵树,所以每两个点之间一定有一个中转点(可能是它们本身)!

这个中转点就是它们的最近公共祖先。(图中绿色的那个点)



两个点之间的路径显然。


怎么求LCA?

暴力方法2

先dfsO(N)记录它的父亲。

两端同时暴力往上跳,每到一个点就打一个标记,跳到打过标记的点时退出,这个点就是LCA。



但速度较慢。设两个点为x和y,深度为deep[x]和deep[y]。那么将最多会有abs(deep[x]-deep[y])个没有用的点被搜到(比如这个图的第5步实际上是没用的)。那么,我们能不能不搜到这些没用的点?

当然可以!

暴力方法3

首先用dfsO(N)预处理出每个点的深度(它们的父亲也可以同时处理)。

先挑一个比较深的点,往上跳到与另一个点深度相同的位置。然后两边同时往上面暴力,相遇的点即答案。

然而还是过不了。看看例题(LCA模板题)。这种方法只有70分。因为每次都要搜一遍,很慢。

如果数据出了一条链来卡,就跑得超慢。

这也不行,那怎么办?(读者:说了这么久还是在将暴力,你几个意思啊?)


倍增求LCA

求LCA有几种方法,在网上我见到了tarjan(离线),RMQ转LCA,还有树链剖分。我介绍一个方法,叫倍增。

设f[i][j]表示点i往上的第2^j个祖先。

首先我们用dfsO(NlgN)求出f数组。式子:f[i][j]=f[f[i][j-1]][j-1]。不解释。

然后我们就可以优美地倍增啦!首先,原来的套路,将两个点跳到同一深度(跳到同一深度的过程也是几个几个跳)。然后将j从大到小枚举,若f[x][j]!=f[y][j],则跳过去。否则就别跳,不然可能会跳过LCA。

最终的答案为f[x][0](f[y][0]一样)。因为在这种限制下,不可能出现x==y的情况,除它们在同一条链上,如下图



这种情况可以特判。因为你在统一它们的深度后,它们就已经重合了。

时间复杂度:O(NlgN+QlgN)

空间复杂度:O(NlgN)

NlgN为dfs预处理的时间,Q是询问次数。


代码实现

例题 P3379【模板】最近公共祖先(LCA)

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
int n,m,s;
struct EDGE
{
int x,y;
EDGE* las;
} e[1000001];//前向星存边
int ne;
EDGE* last[500001];
int f[500001][21];
int deep[500001];
void make_tree(int,int,int);
int main()
{
scanf("%d%d%d",&n,&m,&s);
int i,x,y;
for (i=1;i<n;++i)
{
scanf("%d%d",&x,&y);
e[++ne]={x,y,last[x]};
last[x]=e+ne;
e[++ne]={y,x,last[y]};
last[y]=e+ne;
}
make_tree(s,0,0);
int j,k,tx,ty;
for (i=1;i<=m;++i)
{
scanf("%d%d",&x,&y);
if (deep[x]<deep[y])
swap(x,y);//确保x为深度较大的那个点
k=deep[x]-deep[y];
j=0;
while (k)
{
if ((k&1))
x=f[x][j];
k>>=1;
++j;
}//这段代码起了将两点的深度统一的作用。不知道这样打的原因的同学可以想想快速幂。当然也可以向下面那样打for。两种都可以。
if (x==y)
{
printf("%d\n",x);
continue;
}
for (j=int(log2(deep[x]));j>=0;--j)//若这里像上面那样打while会错。原因不解释。
if (f[x][j]!=f[y][j])
{
x=f[x][j];
y=f[y][j];
}
printf("%d\n",f[x][0]);
}
}
void make_tree(int t,int fa,int de)
{
f[t][0]=fa;
int i,j;
for (i=1,j=2;j<=de;++i,j<<=1)
f[t][i]=f[f[t][i-1]][i-1];//处理处f数组
deep[t]=de;
EDGE* ei;
for (ei=last[t];ei;ei=ei->las)
if (ei->y!=fa)
make_tree(ei->y,t,de+1);
}

倍增(在线)求LCA的更多相关文章

  1. 倍增法求LCA

    倍增法求LCA LCA(Least Common Ancestors)的意思是最近公共祖先,即在一棵树中,找出两节点最近的公共祖先. 倍增法是通过一个数组来实现直接找到一个节点的某个祖先,这样我们就可 ...

  2. HDU 2586 倍增法求lca

    How far away ? Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)To ...

  3. 倍增法求lca(最近公共祖先)

    倍增法求lca(最近公共祖先) 基本上每篇博客都会有参考文章,一是弥补不足,二是这本身也是我学习过程中找到的觉得好的资料 思路: 大致上算法的思路是这样发展来的. 想到求两个结点的最小公共祖先,我们可 ...

  4. Misha, Grisha and Underground CodeForces - 832D (倍增树上求LCA)

    Misha and Grisha are funny boys, so they like to use new underground. The underground has n stations ...

  5. 树上倍增法求LCA

    我们找的是任意两个结点的最近公共祖先, 那么我们可以考虑这么两种种情况: 1.两结点的深度相同. 2.两结点深度不同. 第一步都要转化为情况1,这种可处理的情况. 先不考虑其他, 我们思考这么一个问题 ...

  6. 倍增 Tarjan 求LCA

                                                                                                         ...

  7. SPOJ 10628 Count on a tree(Tarjan离线 | RMQ-ST在线求LCA+主席树求树上第K小)

    COT - Count on a tree #tree You are given a tree with N nodes.The tree nodes are numbered from 1 to  ...

  8. SPOJ COT2 Count on a tree II (树上莫队,倍增算法求LCA)

    题意:给一个树图,每个点的点权(比如颜色编号),m个询问,每个询问是一个区间[a,b],图中两点之间唯一路径上有多少个不同点权(即多少种颜色).n<40000,m<100000. 思路:无 ...

  9. 倍增\ tarjan求lca

    对于每个节点v,记录anc[v][k],表示从它向上走2k步后到达的节点(如果越过了根节点,那么anc[v][k]就是根节点). dfs函数对树进行的dfs,先求出anc[v][0],再利用anc[v ...

  10. 在线倍增法求LCA专题

    1.cojs 186. [USACO Oct08] 牧场旅行 ★★   输入文件:pwalk.in   输出文件:pwalk.out   简单对比时间限制:1 s   内存限制:128 MB n个被自 ...

随机推荐

  1. org.mybatis总是认不出来的原因,pom.xml中有

    idea打开maven project,然后就华丽丽的看见下载失败,怎么刷新都没有用. 请按以下步骤完成即可: https://blog.csdn.net/ZhengYanFeng1989/artic ...

  2. 2019 牛客多校第一场 C Euclidean Distance ?

    题目链接:https://ac.nowcoder.com/acm/contest/881/C 题目大意 给定 m 和 n 个整数 ai,$-m \leq a_i \leq m$,求$\sum\limi ...

  3. Python生成Windows可执行exe文件

    环境 python3.6.5 pyinstaller3.5 windows 10 下载地址 python:https://www.python.org/ftp/python/3.6.5/python- ...

  4. [14]APUE:API for Mysql

    库:/usr/lib64/libmysqlclient.so.#.#... 头文件:/usr/lib64/mysql/mysql.h 一.建立连接 MYSQL *mysql_init(MYSQL *) ...

  5. Unity中DoTween的使用

    在Unity手游开发中,经常用到插值运算,我们可以使用Mathf.Lerp自行去实现效果,但是使用插件提高了我们的开发效率,这里归结一下DoTween的基本使用方式以及效果说明: 直接代码: usin ...

  6. iOS开发系列-LLVM、Clang

    LLVM LLVM计划启动于2000年,最初由University of Illinois at Urbana-Champaign的Chris Lattner主持开展. 我们可以认为LLVM是一个完整 ...

  7. iOS开发系列-异常处理

    概述 在开发中经常调用苹果的API遇到数组越界.实例方法不存在运行时等致命错误,此时程序直接奔溃.其实苹果是在函数内部抛出了一个异常.这样告诉开发者需要检查代码做修改.同样在我们自己封装一些框架或者功 ...

  8. CTR预估的常用方法

    1.CTR CTR预估是对每次广告的点击情况做出预测,预测用户是点击还是不点击. CTR预估和很多因素相关,比如历史点击率.广告位置.时间.用户等. CTR预估模型就是综合考虑各种因素.特征,在大量历 ...

  9. 构造流量图+乱搞——cf990F

    /* 结论1:有解的充要条件是所有点权之和为0 结论2:删掉环上的一条边,只要将这个环上的其余边都减去这条边的边权,那么这个图仍是等价的 从原图网络中构造出一棵带权值的树即可,其他边权都设置为0 通过 ...

  10. 初步了解Redis

    参考: https://juejin.im/post/5b4dd82ee51d451925629622?utm_source=gold_browser_extension https://www.cn ...