LCA(Lowest Common Ancestor 最近公共祖先)定义如下:在一棵树中两个节点的LCA为这两个节点所有的公共祖先中深度最大的节点。

比如这棵树

结点5和6的LCA是2,12和7的LCA是1,8和14的LCA是4。

这里讲一下用树链剖分来求LCA。

先想一下,若要求结点13和4的LCA,那很显然是4,因为他们在一条重链上。所谓的重链,就是取每个结点u的所有子节点中,子树最大的子结点v,然后将边(u,v)作为重边,其余边作为轻边,重边构成的链就是重链。子树最大就是指该点所得孩子结点最多(这里要包括他自己)。

我们先找出所有的重链。

可见这棵树有7条重链(包括一条链只有一个结点的)。每一条重链的顶点就是该链上深度最小的结点。

而树链剖分的目标就是将要求的两个点转换到一条重链上,这样LCA就是该条重链上深度较小的结点了。

具体实现步骤拿第一幅图中的结点12和14举例。首先要比较的是12和14所在链的顶点的深度,可见12所在链的顶点更深,此时将12跳到它的顶点12的父亲结点6。然后再比较6所在链的顶点和14所在链的顶点,循环下去直到两个点到同一个链上,最后比较,收尾。

这就是树链剖分的基本思想了,那我就开始写了。

首先跑两遍dfs,第一遍是建树和建链,第二遍是记录每一个结点的顶点(这样就知道该点所在链的顶点的深度了)。然后就是用上述思想求LCA。

我们以洛谷的板子为例,传送门:https://www.luogu.org/problemnew/show/P3379

上代码(懒得用邻接表存图了,上vector)

其中vis数组是为了解决无向图存两次边的问题。

 #include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int maxn = 5e5 + ;
vector<int>v[maxn];
/*第一遍dfs主要来维护以下这些数组,size[now]指结点now的子树大小,dep[now]指结点now的深度,
Maxson[now]指now所在链上结点now的下一个结点(用来建链) */
int vis[maxn], size[maxn], dep[maxn], fa[maxn], Maxson[maxn];
void dfs1(int now)
{
vis[now] = ; size[now]= ;
for(int i = ; i < v[now].size(); ++i)
if(!vis[v[now][i]])
{
dep[v[now][i]] = dep[now] + ;
fa[v[now][i]] = now;
dfs1(v[now][i]);
size[now] += size[v[now][i]]; //结点now的子树大小就是他所有孩子结点的大小之和加1(包括自己)
if(size[v[now][i]] > size[Maxson[now]]) Maxson[now] = v[now][i]; //选重边
}
}
int path[maxn];
void dfs2(int now)
{
vis[now] = ;
for(int i = ; i < v[now].size(); ++i)
if(!vis[v[now][i]])
{
if(Maxson[now] == v[now][i]) path[v[now][i]] = path[now];
else path[v[now][i]] = v[now][i]; //新开辟一条链
dfs2(v[now][i]);
}
}
int lca(int x, int y)
{
while(path[x] != path[y]) //若不在一条链上
{
if(dep[path[x]] > dep[path[y]]) x = fa[path[x]];
else y = fa[path[y]];
}
return dep[x] < dep[y] ? x : y;
}
int main()
{
int n, m, s; scanf("%d%d%d", &n, &m, &s);
for(int i = ; i < n; ++i)
{
int a, b; scanf("%d%d", &a, &b);
v[a].push_back(b); v[b].push_back(a);
}
dep[s] = ; memset(vis, , sizeof(vis));
dfs1(s);
path[s] = s; memset(vis, , sizeof(vis));
dfs2(s);
for(int i = ; i <= m; ++i)
{
int a, b; scanf("%d%d", &a, &b);
printf("%d\n", lca(a, b));
}
return ;
}

我们再来分析一下时间复杂度:任意一个结点到根的路径,每遇到一条轻边,子树大小就至少翻一倍,所以最坏情况下是O(logn),很牛吧?

洛谷的这个毒瘤板子题,我以前用RMQ和树上倍增写都会T两个点,加快读快输开氧气勉强过了,但是很不爽。直到有一天我会了树剖后,竟然直接AC,贼激动。

那我就讲到这了。啊对了,他有一个缺点,难写(还是RMQ简单)

LCA树链剖分的更多相关文章

  1. Count on a tree SPOJ 10628 主席树+LCA(树链剖分实现)(两种存图方式)

    Count on a tree SPOJ 10628 主席树+LCA(树链剖分实现)(两种存图方式) 题外话,这是我第40篇随笔,纪念一下.<( ̄︶ ̄)↗[GO!] 题意 是说有棵树,每个节点上 ...

  2. [BZOJ3626] [LNOI2014]LCA(树链剖分)

    [BZOJ3626] [LNOI2014]LCA(树链剖分) 题面 给出一棵N个点的树,要求支持Q次询问,每次询问一个点z与编号为区间[l,r]内的点分别求最近公共祖先得到的最近公共祖先深度和.N, ...

  3. BZOJ 3626: [LNOI2014]LCA [树链剖分 离线|主席树]

    3626: [LNOI2014]LCA Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 2050  Solved: 817[Submit][Status ...

  4. Codeforces Round #329 (Div. 2) D. Happy Tree Party LCA/树链剖分

    D. Happy Tree Party     Bogdan has a birthday today and mom gave him a tree consisting of n vertecie ...

  5. BZOJ 3626: [LNOI2014]LCA( 树链剖分 + 离线 )

    说多了都是泪啊...调了这么久.. 离线可以搞 , 树链剖分就OK了... -------------------------------------------------------------- ...

  6. [CodeVS2370] 小机房的树 (LCA, 树链剖分, LCT)

    Description 小机房有棵焕狗种的树,树上有N个节点,节点标号为0到N-1,有两只虫子名叫飘狗和大吉狗,分居在两个不同的节点上.有一天,他们想爬到一个节点上去搞基,但是作为两只虫子,他们不想花 ...

  7. BZOJ3626[LNOI2014]LCA——树链剖分+线段树

    题目描述 给出一个n个节点的有根树(编号为0到n-1,根节点为0).一个点的深度定义为这个节点到根的距离+1.设dep[i]表示点i的深度,LCA(i,j)表示i与j的最近公共祖先.有q次询问,每次询 ...

  8. bzoj 3626 : [LNOI2014]LCA (树链剖分+线段树)

    Description 给出一个n个节点的有根树(编号为0到n-1,根节点为0).一个点的深度定义为这个节点到根的距离+1.设dep[i]表示点i的深度,LCA(i,j)表示i与j的最近公共祖先.有q ...

  9. 【bzoj3626】[LNOI2014]LCA 树链剖分+线段树

    题目描述 给出一个n个节点的有根树(编号为0到n-1,根节点为0).一个点的深度定义为这个节点到根的距离+1.设dep[i]表示点i的深度,LCA(i,j)表示i与j的最近公共祖先.有q次询问,每次询 ...

随机推荐

  1. Session会话保持机制的原理与Tomcat Session共享的几种实现方式(Session Cluster、memcached+MSM)

    一.Session的定义 在计算机科学中,特别是在网络中,session是两个或更多个通信设备之间或计算机和用户之间的临时和交互式信息交换.session在某个时间点建立,然后在之后的某一时间点拆除. ...

  2. python元祖操作和内置方法

    1 元祖:元祖可以理解为一个不可变的列表 2 用途:用于存放多个值,当存放的多个值只有读的需求而没有改的需求时用元祖最合适 3 定义:在()内用逗号分隔开多个任意类型的值.注意:当只有一个元素的时候, ...

  3. vue-cli+webpack项目,修改项目名称

    使用vue-cli+webpack创建的项目,修改文件名称或者更改文件的位置,运营时会报错,是因为npm项目,在安装依赖(node_nodules)时,会记录当前的文件路径,当修改之后就无法正常启动. ...

  4. spring cloud config与eureka配合使用

    前面两篇介绍了Spring Cloud Config服务端和客户端的简单配置,本篇介绍Spring Cloud Config与Eureka配合使用 前言 默认情况下,配置客户端启动时,都是通过配置属性 ...

  5. Java高级类特性(二)

    一.static关键字 static关键字用来声明成员属于类,而不是属于类的对象.1). static (类)变量类变量可以被类的所有对象共享,以便与不共享的成员变量区分开来. static变量也称作 ...

  6. 【Java基础】14、位运算之——按位与(&)操作——(快速取模算法)

    学习redis 字典结构,hash找槽位 求槽位的索引值时,用到了 hash值 & sizemask操作, 其后的scan操作涉及扫描顺序逻辑,对同模的槽位 按一定规则扫描! 其中涉及位运算 ...

  7. python中的清屏函数

    一:cmd中python的清屏函数 import os os.system("cls") cmd中演示 1.在cmd中输入命令行: 2.执行后: 3.为什么会遗留一个0? 因为函数 ...

  8. node 链接mysql(自动链接)

    Node.js与MySQL交互操作有很多库 felixge/node-mysql 常用 cnpm i mysql --save dev 1.打开mysql 必须要有,我这里面用的是wamp.可视化用的 ...

  9. CSS中的sprites

    CSS的sprites(雪碧图,精灵图)          *三个名字都要记住 定义:就是将界面上需要的小的导航按钮图标,小图标整合成一张背景图片 然后用background-position来实现背 ...

  10. 【读书笔记】iOS-处理内存警告

    -(void)didReceiveMemoryWarning{ [super didReceiveMemoryWarning]; } 在这里你需要释放掉所有占用了很大内存的对象,如果你忽略了这个警告, ...