树链剖分与倍增求\(LCA\)

首先我要吐槽机房的辣基供电情况,我之前写了一上午,马上就要完成的时候突然停电,然后\(GG\)成了送链剖分

其次,我没歧视\(tarjan LCA\)

1.倍增求\(LCA\)

理解较为简单的一种方法,但速度略慢

倍增是啥?

每个数字都可以拆成几个二的整数次的和,我们可以找出每个数字是由哪几个二的整数次的数合成的

比如说\(14 _ {10} = 1110_2 = 1000 _2 + 100 _2 + 10 _2 = 8 _ {10} + 4 _{10} + 2 _{10}\)

那么我们如果要统计一段长度为十四的区间的最小值,我们就可以先统计前八个数的最小值,再统计之后的四个,再统计之后的两个。

我们可以用\(f[i][j]\)表示从\(i\)开始往后\(2^j\)长度的最小值

给宁康康代码

for( int i = 1; i <= 23; i++ ){
for( rint j = 1; j <= n; j++ ){
//a[i][j]存的是i往后2的j次长度的区间的右节点是哪儿
f[i][j] = min( f[i][j - 1], f[a[i][j - 1]][j - 1] );
}
}

下面这个东西是啥意思呢

f[i][j - 1], f[a[i][j - 1]][j - 1]

\(2^j = 2^{j-1} +2^{j - 1}\) 比如说 \(2^4 = 2 ^ 3 +2 ^ 3\)

树上倍增

在每个叶节点到根节点的链上做倍增

图片演示一哈(如果打开我的博客就会发现我这种蒟蒻说不清话只会画图

应该比较显然吧???

我们在求\(LCA\)前\(dfs\)一遍,统计出每个叶节点的\(f[i][1]\)(也就是父节点)和\(dep[i]\)(就是该节点所处深度,规定根节点深度为1)。然后跑一遍倍增,预处理每个叶节点的向上\(2^i\)个祖宗是谁。

然后倍增求\(LCA\),我们可以先看两个点是否在同一深度,不在的话就把比较低的那个点往上走一走,直到走到同一深度。注意在跳的时候要从大到小枚举,给宁康康代码,宁再把上面的\(14\)那个例子带进去从\(1\)到\(20\)枚举一下子就懂了

inline int lca( int x, int y ){
if( dep[x] < dep[y] ) swap( x, y );
for( rint i = 20; i >= 0; i-- ){
if( dep[f[x][i]] >= dep[y] ) x = f[x][i];
}
if( x == y ) return x;
for( rint i = 20; i >= 0; i-- ){
if( f[x][i] == f[y][i] ) continue; //如果跳的一样的话就可能是LCA或者是LCA的祖先,所以先跳到最后一个不一样的,再往上跳一个
else x = f[x][i], y = f[y][i];
}
return f[x][0];
}

\(AC\)代码

#include<bits/stdc++.h>
using namespace std;
#define rint register int
int n, m, s, cnt, dep[500005], f[500005][23], head[500005];
struct edge{
int to, nxt;
}a[500005<<1];
inline int read( void ){
int re = 0, f = 1;
char ch = getchar();
while( ch > '9' || ch < '0' ){
if( ch == '-' ) f = -1;
ch = getchar();
}
while( ch >= '0' && ch <= '9' ){
re = re * 10 + ch - '0';
ch = getchar();
}
return re * f;
}
inline void addedge( int x, int y ){
a[++cnt].to = y;
a[cnt].nxt = head[x];
head[x] = cnt;
}
inline void dfs( int x, int fa ){
dep[x] = dep[fa] + 1;
f[x][0] = fa;
for( rint i = 1; ( 1 << i ) <= dep[x]; i++ ){
f[x][i] = f[f[x][i - 1]][i - 1];
}
for( rint i = head[x]; i; i = a[i].nxt ){
int v = a[i].to;
if( v == fa ) continue;
dfs( v, x );
}
return ;
}
inline int lca( int x, int y ){
if( dep[x] < dep[y] ) swap( x, y );
for( rint i = 20; i >= 0; i-- ){
if( dep[f[x][i]] >= dep[y] ) x = f[x][i];
}
if( x == y ) return x;
for( rint i = 20; i >= 0; i-- ){
if( f[x][i] == f[y][i] ) continue;
else x = f[x][i], y = f[y][i];
}
return f[x][0];
}
int main( void ){
n = read(); m = read(); s = read();
for( rint i = 1; i <= n - 1; i++ ){
int x, y; x = read(); y = read();
addedge( x, y ); addedge( y, x );
}
dfs( s, 0 );
int u, v;
for( rint i = 1; i <= m; i++ ){
u = read(); v = read();
cout << lca( u ,v ) << endl;
}
return 0;
}

树链剖分

树链剖分其实有好多种剖分方法,但这里只介绍轻重边剖分

含义

一个节点只能有一个重儿子。

链 : 连续的重/轻边构成一条链。(图中从\(1\)到\(14\)即一条重链)

\(dep[i]\) : \(i\)节点的深度,规定根节点深度为\(1\)。

\(fa[i]\) : \(i\)节点的父亲。

\(son[i]\) : \(i\)节点的重儿子。

\(siz[i]\) : 以\(i\)节点为根的子树的大小。

\(top[i]\) : \(i\)所在链的根。(图中从\(1\)到\(14\)的链的根为\(1\))

重边 : 以\(i\)节点的儿子中\(siz\)最大的儿子到\(i\)的连边,即图中的粗边(该儿子也叫重儿子)。

轻边 : 处重边外的其他边。

步骤

首先\(dfs\)一遍,求出\(dep\),\(fa\),\(son\),\(siz\),容易实现,给宁康代码

inline void dfs1( int now, int father, int de ){
siz[now] = 1; fa[now] = father; dep[now] = de;
int maxn = -1;
for( rint i = 0; i < vec[now].size(); i++ ){
int v = vec[now][i];
if( v == father ) continue ;
dfs1( v, now, de + 1 );
siz[now] += siz[v];
if( siz[v] > maxn ){
maxn = siz[v];
son[now] = v;
}
}
}

然后再\(dfs\)一遍,处理出每个点的\(top\),也就是所在链的顶点,其中轻链的顶点是它自己。

给宁康康代码

inline void dfs2( int now, int topf ){
top[now] = topf;
if( !son[now] ) return ;
dfs2( son[now], topf );
for( rint i = 0; i < vec[now].size(); i++ ){
int v = vec[now][i];
if( v == fa[now] || v == son[now] ) continue;
dfs2( v, v );
}
}

如果一个点要跳到它的\(lca\),就一定会跳到它的\(lca\)所在链(废话……

那么我们要判定是否已经找到\(lca\),就只需要看当前两点\(xy\)是否在同一条链上,其实就是看两个点的\(top\)是否相等,如果不相等的话,我们就让深度大的点一次性跳完一整条重链,然后再跳一步,走上另一条重链,再重复以上比较\(top\),跳重链的过程

我们可以发现非叶节点一定在某条重链上,所以我们一次性跳完一条重链,再跳一步,就会跳上另一条重链,所以查找\(lca\)的复杂度是小于\(logn\)的

给宁康康代码

inline int lca( int x, int y ){
while( top[x] != top[y] ){
if( dep[top[x]] < dep[top[y]] ) swap( x, y );
x = fa[top[x]];
}
if( dep[x] > dep[y] ) return y;
return x;
}

全部代码:

#include<bits/stdc++.h>
using namespace std;
#define rint register int
int T, n, m;
int son[1000010], fa[1000010], siz[1000010], dep[1000010], top[1000010];
vector< int > vec[1000010];
inline int read( void ){
int re = 0, f = 1; char ch = getchar();
while( ch > '9' || ch < '0' ){
if( ch == '-' ) f = -1;
ch = getchar();
}
while( ch >= '0' && ch <= '9' ){
re = re * 10 + ch - '0';
ch = getchar();
}
return re * f;
}
inline void dfs1( int now, int father, int de ){
siz[now] = 1; fa[now] = father; dep[now] = de;
int maxn = -1;
for( rint i = 0; i < vec[now].size(); i++ ){
int v = vec[now][i];
if( v == father ) continue ;
dfs1( v, now, de + 1 );
siz[now] += siz[v];
if( siz[v] > maxn ){
maxn = siz[v];
son[now] = v;
}
}
}
inline void dfs2( int now, int topf ){
top[now] = topf;
if( !son[now] ) return ;
dfs2( son[now], topf );
for( rint i = 0; i < vec[now].size(); i++ ){
int v = vec[now][i];
if( v == fa[now] || v == son[now] ) continue;
dfs2( v, v );
}
}
inline int lca( int x, int y ){
while( top[x] != top[y] ){
if( dep[top[x]] < dep[top[y]] ) swap( x, y );
x = fa[top[x]];
}
if( dep[x] > dep[y] ) return y;
return x;
}
int main( void ){
n = read(); m = read();
for( rint i = 1; i < n; i++ ){
int u, v; u = read(); v = read();
vec[u].push_back( v );
vec[v].push_back( u );
}
dfs1( 1, 1, 1 );
dfs2( 1, 1 );
for( rint i = 1; i <= m; i++ ){
int x, y; x = read(); y = read();
printf( "%d\n", lca( x, y ) );
}
return 0;
}

如果要求树上两点最短距离,可以求\(lca\)

\(dis = dep[x] + dep[y] - 2 * lca\)

树链剖分与倍增求LCA的更多相关文章

  1. NOIP2015 运输计划 - 二分 + 树链剖分 / (倍增 + 差分)

    BZOJ CodeVS Uoj 题目大意: 给一个n个点的边带权树,给定m条链,你可以选择树中的任意一条边,将它置为0,使得最长的链长最短. 题目分析: 最小化最大值,二分. 二分最短长度mid,将图 ...

  2. 树链剖分(附带LCA和换根)——基于dfs序的树上优化

    .... 有点懒: 需要先理解几个概念: 1. LCA 2. 线段树(熟练,要不代码能调一天) 3. 图论的基本知识(dfs序的性质) 这大概就好了: 定义: 1.重儿子:一个点所连点树size最大的 ...

  3. 模板 树上求LCA 倍增和树链剖分

    //233 模板 LCA void dfs(int x,int f){ for(int i=0;i<E[x].size();i++){ int v = E[x][i]; if(v==f)cont ...

  4. 树链剖分求LCA

    树链剖分中各种数组的作用: siz[]数组,用来保存以x为根的子树节点个数 top[]数组,用来保存当前节点的所在链的顶端节点 son[]数组,用来保存重儿子 dep[]数组,用来保存当前节点的深度 ...

  5. 【树链剖分】洛谷P3379 树链剖分求LCA

    题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每 ...

  6. NOIP2016提高组Day1T2 天天爱跑步 树链剖分 LCA 倍增 差分

    原文链接https://www.cnblogs.com/zhouzhendong/p/9275606.html 题目传送门 - 洛谷P1600 题目传送门 - LOJ#2359 题目传送门 - Vij ...

  7. 树链剖分 树剖求lca 学习笔记

    树链剖分 顾名思义,就是把一课时分成若干条链,使得它可以用数据结构(例如线段树)来维护 一些定义: 重儿子:子树最大的儿子 轻儿子:除了重儿子以外的儿子 重边:父节点与重儿子组成的边 轻边:除重边以外 ...

  8. 树链剖分 (求LCA,第K祖先,轻重链剖分、长链剖分)

      2020/4/30   15:55 树链剖分是一种十分实用的树的方法,用来处理LCA等祖先问题,以及对一棵树上的节点进行批量修改.权值和查询等有奇效. So, what is 树链剖分? 可以简单 ...

  9. LCA 倍增||树链剖分

    方法1:倍增 1498ms #include <iostream> #include <cstdio> #include <algorithm> #include ...

随机推荐

  1. 接受H0的坏处|试验误差|置信度由来|

    生物统计与实验设计 置信度(0.05 0.01)是通过实验次数估计值的分布得到的,它是整个分布的期望,这个值的确立需要具体情况具体分析. 肯定很难,因为否定一次很容易.虽然如果没有否定(eg:得到p= ...

  2. Visual Studio通过Cordova支持混合跨平台移动开发

    Microsoft在Visual Studio 2013 Update 2中添加了对混合跨平台移动应用程序的本地支持. Microsoft早在2011年就已经开始了与PhoneGap的合作,那时候是为 ...

  3. springdatajpa 认识以及使用方式

    1.spingdatajpa是什么? Spring Data JPA 是 Spring 基于 ORM 框架.JPA 规范的基础上封装的一套JPA应用框架(即上述的:JPA的实现产品),可使开发者用极简 ...

  4. LG_3457_[POI2007]POW-The Flood

    题目描述 Description 你手头有一张该市的地图.这张地图是边长为 m∗n 的矩形,被划分为m∗n个1∗1的小正方形.对于每个小正方形,地图上已经标注了它的海拔高度以及它是否是该市的一个组成部 ...

  5. labview的bool(布尔)按键机械属性

    在学习LabVIEW(简称LV)时,布尔控件是常用的控件.布尔控件分为按钮型控件和开关型控件,LV内部并没有区分按钮型还是开关型.这两种布尔控件可以根据需要相互转换,通过配置布尔控件的机械动作属性来实 ...

  6. 了解DocumentFragment 给我们带来的性能优化

    首先我们需要了解 DocumentFragment 是什么? w3c 上面的详细解释:link here 我把关键点写下来了: DocumentFragment 节点不属于文档树,继承的 parent ...

  7. 关于angular跳转路由之后不能自动回到顶部的解决方法

    Question: angular2 scroll top on router change 当我们在第一个路由滑动到底部当我们点击导航跳转到另一个路由时页面没有回到顶部而是保持上一个路由的滚动位置, ...

  8. Java Servlet XML文件配置

  9. 关于log4j中log4j.properties和log4j.xml的加载顺序

    如果采用log4j输出日志,要对log4j加载配置文件的过程有所了解. log4j启动时,默认会寻找source folder下的log4j.xml配置文件,若没有,会寻找log4j.properti ...

  10. ssm框架下怎么批量删除数据?

    ssm框架下批量删除怎么删除? 1.单击删除按钮选中选项后,跳转到js函数,由函数处理 2. 主要就是前端的操作 js 操作(如何全选?如何把选中的数据传到Controller中) 3.fun()函数 ...