树剖LCA讲解
LCA的类型多种多样,只说我知道的,就有倍增求LCA,tarjin求LCA和树链剖分求LCA,当然,也还有很多其他的方法。
其中最常用,速度最快的莫过于树链剖分的LCA了。
树链剖分,首先字面理解一下,什么是树链剖分。
就是把一棵树剖分为若干条链,然后利用数据结构(树状数组,SBT,Splay,线段树等等)去维护每一
条链,复杂度为O(logn)
那么,树链剖分的第一步当然是对整棵树进行遍历,预处理一些要用的变量。
void dfs(int now){
siz[now]=;
deep[now]=deep[dad[now]]+;
for(int i=head[now];i;i=net[i])
if(to[i]!=dad[now]){
dad[to[i]]=now;
dfs(to[i]);
siz[now]+=siz[to[i]];
}
}
其中dad[i]=j表示节点i的爸爸是j,deep[i]表示节点i的深度,siz[i]表示以节点i为祖先的子树的大小(也就是这个子树里节点的个数)
第二步就是对树进行轻重边的划分,这样我们就可以保证每一个点属于且只属于一条链。
在这棵树中其实,只有重链是我们用的到的,轻链一般就被忽略了。
而 我们一般是通过儿子个数的多少来划分出轻重边的,在所有的儿子结点中,各以他们为祖先的子树的节点的个数多的儿子节点与其父亲的连边就是一条重边。这句话有点绕口,看图理解一下:(因为这棵树不一定是二叉树,一个节点可能有多个儿子,这这例子是一个二叉树的,不太全面。)
在这个图里面6和9的连边就是一条重边,而6和7的连边就是一条轻边。类似的,9这个节点有两个儿子,以这两个儿子为祖先的子树的大小是相同的,所以随便找一条
边作为重边就可以了,而7只有一个子节点所以7和8的链理所因当就是重链了。
对于这样的一颗树来说,他的轻重链分布如下所示:其中,灰色粗线表示重边,黑色细线表示轻边。
这是他的边的分布情况:
这是找轻重链的代码:其中top[i]表示点i沿他的重边上去,最高能到达的点。
比如上面的图里面每个点的top分别是:1,2,2,2,1,1,7,7,1,10,1,12,12
void dfs1(int x){
int t=;
if(!top[x]) top[x]=x;
for(int i=head[x];i;i=net[i])
if(to[i]!=dad[x]&&siz[to[i]]>siz[t])
t=to[i];
if(t){
top[t]=top[x];
dfs1(t);
}
for(int i=head[x];i;i=net[i])
if(to[i]!=dad[x]&&t!=to[i])
dfs1(to[i]);
}
找完轻重链后,就可以进行下一步操作了,找最近公共祖先。
先看一下代码:
int lca(int x,int y){
for(;top[x]!=top[y];){
if(deep[top[x]]<deep[top[y]])
swap(x,y);
x=dad[top[x]];
}
if(deep[x]>deep[y])
swap(x,y);
return x;
}
代码是非常好理解的,就是一个while语句,当两个点位于一条重链上的时候结束操作。
否则的话,深度深的点,顺着他所在的重链向上跳。跳到这条重链的上一个点的位置。在两个点位于一条链上之后,深度浅的点的位置,就是我们要找的公共祖先。
还是这个图,如果要求8和11的最近公共祖先。第一步就是让8顺着边向上跳他会跳到为位置6的点上去,然后6和11位于同一条链上,6的深度浅,6就是我们要求的最近公共祖先。
再举一个复杂点的例子:如果要求10和13的最近公共祖先要怎么呢?
首先,找到两个点中深度较深的点,是10,10号点向上跳,跳到9号点,然后是13号点向上跳,回跳到5号点,这是5和9位于同一条链上,5号点就是我们所求的最近公共祖先。
有人可能会产生这样的问题:我在跳边的时候,为什么要跳到重链顶点的父亲,而不跳到重链顶点,这样万一错过了最近公共祖先肿么办?
那就要请你自己好好考虑了,因为这个问题在上面已经回答过了。
第一个问题:如果跳到顶点的话,像是上面的图中的10号点就跳不动了,那公共祖先就出不来了。
第二个问题:不会。因为除了叶子结点,每一个节点都一定属于一条重链。
答案
题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入输出格式
输入格式:
第一行包含三个正整数N、M、S,分别表示树的结点个数、询问的个数和树根结点的序号。
接下来N-1行每行包含两个正整数x、y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树)。
接下来M行每行包含两个正整数a、b,表示询问a结点和b结点的最近公共祖先。
输出格式:
输出包含M行,每行包含一个正整数,依次为每一个询问的结果。
输入输出样例
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=10,M<=10
对于70%的数据:N<=10000,M<=10000
对于100%的数据:N<=500000,M<=500000
样例说明:
该树结构如下:
第一次询问:2、4的最近公共祖先,故为4。
第二次询问:3、2的最近公共祖先,故为4。
第三次询问:3、5的最近公共祖先,故为1。
第四次询问:1、2的最近公共祖先,故为4。
第五次询问:4、5的最近公共祖先,故为4。
故输出依次为4、4、1、4、4。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define MAXN 500010
using namespace std;
int n,m,s,tot;
int to[MAXN*],net[MAXN*],head[MAXN];
int dad[MAXN],deep[MAXN],siz[MAXN],top[MAXN];
void add(int u,int v){
to[++tot]=v;net[tot]=head[u];head[u]=tot;
to[++tot]=u;net[tot]=head[v];head[v]=tot;
}
void dfs(int now){
siz[now]=;
deep[now]=deep[dad[now]]+;
for(int i=head[now];i;i=net[i])
if(to[i]!=dad[now]){
dad[to[i]]=now;
dfs(to[i]);
siz[now]+=siz[to[i]];
}
}
void dfs1(int x){
int t=;
if(!top[x]) top[x]=x;
for(int i=head[x];i;i=net[i])
if(to[i]!=dad[x]&&siz[to[i]]>siz[t])
t=to[i];
if(t){
top[t]=top[x];
dfs1(t);
}
for(int i=head[x];i;i=net[i])
if(to[i]!=dad[x]&&t!=to[i])
dfs1(to[i]);
}
int lca(int x,int y){
for(;top[x]!=top[y];){
if(deep[top[x]]<deep[top[y]])
swap(x,y);
x=dad[top[x]];
}
if(deep[x]>deep[y])
swap(x,y);
return x;
}
int main(){
scanf("%d%d%d",&n,&m,&s);
for(int i=;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
}
dfs(s);
dfs1(s);
for(int i=;i<=n;i++) cout<<top[i]<<" ";
for(int i=;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
cout<<lca(u,v)<<endl;
}
}
/*
13 1 1
1 2
2 3
3 4
1 5
5 6
6 7
7 8
6 9
9 10
9 11
5 12
12 13
*/
练习题:
cogs 2478. [HZOI 2016]简单的最近公共祖先
洛谷 P3128 [USACO15DEC]最大流Max Flow
树剖LCA讲解的更多相关文章
- 树剖 lca
GeneralLiu 橙边为轻边 红边为重边 绿数为每个点的 top 橙数为每个点的编号 步骤 1 先预处理 每个点的 deep深度 size子树大小 dad父节点 2 再预处理 每个点的 to ...
- BZOJ_2286_[Sdoi2011]消耗战_虚树+树形DP+树剖lca
BZOJ_2286_[Sdoi2011]消耗战_虚树+树形DP Description 在一场战争中,战场由n个岛屿和n-1个桥梁组成,保证每两个岛屿间有且仅有一条路径可达.现在,我军已经侦查到敌军的 ...
- bzoj 3307: 雨天的尾巴【树剖lca+树上差分+线段树合并】
这居然是我第一次写线段树合并--所以我居然在合并的时候加点结果WAWAWAMLEMLEMLE--!ro的时候居然直接指到la就行-- 树上差分,每个点建一棵动态开点线段树,然后统计答案的时候合并即可 ...
- 【BZOJ 3626】 [LNOI2014]LCA【在线+主席树+树剖】
题目链接: TP 题解: 可能是我比较纱布,看不懂题解,只好自己想了…… 先附一个离线版本题解[Ivan] 我们考虑对于询问区间是可以差分的,然而这并没有什么卵用,然后考虑怎么统计答案. 首先LC ...
- tarjan,树剖,倍增求lca
1.tarjan求lca 思想: void tarjan(int u,int f){ for(int i=---){//枚举边 if(v==f) continue; dfs(v); //继续搜 uni ...
- BZOJ 3626 [LNOI2014]LCA:树剖 + 差分 + 离线【将深度转化成点权之和】
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3626 题意: 给出一个n个节点的有根树(编号为0到n-1,根节点为0,n <= 50 ...
- POJ3417Network(LCA+树上查分||树剖+线段树)
Yixght is a manager of the company called SzqNetwork(SN). Now she's very worried because she has jus ...
- 【树剖求LCA】树剖知识点
不太优美但是有注释的版本: #include<cstdio> #include<iostream> using namespace std; struct edge{ int ...
- 树链剖分 树剖求lca 学习笔记
树链剖分 顾名思义,就是把一课时分成若干条链,使得它可以用数据结构(例如线段树)来维护 一些定义: 重儿子:子树最大的儿子 轻儿子:除了重儿子以外的儿子 重边:父节点与重儿子组成的边 轻边:除重边以外 ...
随机推荐
- 关于postman软件的安装与使用
1.这个软件是一个模拟发请求的软件 2.这个软件和这个网站的 json 格式数据有着很好的关系 https://www.json.cn/ 他能帮助我们分解代码, 3.在使用(修改的)过程中发现了一个 ...
- Python/Django 批量下载Excel
一.前提 项目上需求的变更总是时时发生的,应对需求的我们,也只能变更我们代码,所以.继前两篇之后,我们的批量下载诞生了 二.安装 本文使用zipstream库进行压缩,安装方式:pip install ...
- python值函数名的使用以及闭包,迭代器
一.函数名的运用 函数名就是一个变量名,但它是一个特殊的变量名,是一个后面加括号可以执行函数的变量名. def func(): print("我是一个小小的函数") a = fun ...
- linux命令大杂烩之网络管理
在Linux中curl是一个利用URL规则在命令行下工作的文件传输工具,可以说是一款很强大的http命令行工具.它支持文件的上传和下载,是综合传输工具,但按传统,习惯称url为下载工具. 作为一款强力 ...
- Flume 是什么?
Flume是一个分布式.可靠.和高可用的海量日志聚合的系统,支持在系统中定制各类数据发送方,用于收集数据:同时,Flume提供对数据进行简单处理,并写到各种数据接受方(可定制)的能力. 收集.聚合事件 ...
- java 多线程并发系列之 生产者消费者模式的两种实现
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题.该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度. 为什么要使用生产者和消费者模式 在线程世界里,生产者就是生产数据 ...
- Android 在fragment中实现返回键单击提醒 双击退出
尝试用mvp架构加dagger2来重写了一下,大致功能都实现了,还没有全部完成. 项目地址 接近完成的时候,想在天气信息页面实现一个很常见的功能,也就是点击屏幕下方的返回键的时候不是返回到上一个act ...
- Android 检查手机上是否安装了指定的软件(根据包名检测)
Android检查手机上是否安装了指定的软件(根据包名检测) /** * 检查手机上是否安装了指定的软件 * @param context * @param packageName * @return ...
- VC窗口类的销毁-是否需要delete
Windows窗口如果使用new的方法添加之后,在父窗口析构的时候,有些需要delete有些却不需要delete.这个的确有点坑,由于c++的实现,对于每个自己new的对象,我都会delete删除它, ...
- HDU_1023_Train Problem II_卡特兰数
Train Problem II Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others) ...