树链剖分与倍增求\(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. oracle存储过程随笔

    有时间要把最近总结的存储过程的问题总结一下,先占坑: create or replace procedure test_proc2 as cursor cur is select YWR,YWRZJH ...

  2. Linux下文件 ~/.bashrc 和 ~/.bash_profile 和 /etc/bashrc 和 /etc/profile 的区别 | 用户登录后加载配置文件的顺序

    转自 https://blog.csdn.net/secondjanuary/article/details/9206151 文件说明: /ect/profile 此文件为系统的每个用户设置环境信息, ...

  3. SpringBoot多数据源中的分布式事务

    虽然现在微服务越来越流行,我们的系统随之也拆分出来好多的模块功能.这样做的目的其实就是为了弥补单体架构中存在的不足.随着微服务的拆分,肯定设计到分库分表,但这之中肯定设计到分布式事务.最典型的例子就是 ...

  4. ContentType明细对照表(文件类型相关的设置)

    文件扩展名 Content-Type(Mime-Type) 文件扩展名 Content-Type(Mime-Type) .*( 二进制流,不知道下载文件类型) application/octet-st ...

  5. Maven基本概念和操作

    最近在学Java,找来一个开源项目练手,它是用 Spring Boot 搭建的框架,于是去学 Spring Boot,然而 Spring Boot 需要有 Spring 框架和 Maven 的使用经验 ...

  6. Ionic3学习笔记(九)关于 Android 端软键盘弹出后界面被压缩的问题

    本文为原创文章,转载请标明出处 今天做了一个如下图所示的页面. iOS 端毫无 bug,Android 端却出现了问题.当软键盘弹出后,Android 端的 tabs 移到了软键盘的上面,再仔细一看, ...

  7. mongodb 4.0配置认证模块

    use admin db.createUser({user:"root",pwd:"xxx",roles:[{role:"root",db: ...

  8. 机器学习技法笔记(2)-Linear SVM

    从这一节开始学习机器学习技法课程中的SVM, 这一节主要介绍标准形式的SVM: Linear SVM 引入SVM 首先回顾Percentron Learning Algrithm(感知器算法PLA)是 ...

  9. 算法拾遗[4]——STL用法

    主要bb一下优先队列和字符串吧. 哦还有 bitset. 优先队列 定义很容易: priority_queue<int> pq; 内部是一个堆. 基本操作 pq.top() 取堆顶元素; ...

  10. 内核融合:GPU深度学习的“加速神器”

    ​编者按:在深度学习"红透"半边天的同时,当前很多深度学习框架却面临着共同的性能问题:被频繁调用的代数运算符严重影响模型的执行效率. 本文中,微软亚洲研究院研究员薛继龙将为大家介绍 ...