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. 四层和七层负载均衡的特点及常用负载均衡Nginx、Haproxy、LVS对比

    一.四层与七层负载均衡在原理上的区别 图示: 四层负载均衡与七层负载均衡在工作原理上的简单区别如下图: 概述: 1.四层负载均衡工作在OSI模型中的四层,即传输层.四层负载均衡只能根据报文中目标地址和 ...

  2. jQuery中的事件绑定的几种方式

    jQuery目前有on(),bind(),delegate(),live()四种绑定方式,但是随着版本的不断更新,有的方式也相应的被淘汰掉 [band()方式绑定] 3.0版本之前的绑定方式比较常用的 ...

  3. MVC 【Razor 视图引擎】基础操作 --页面跳转,传值,表单提交

    ASPX  与  Razor  仅仅是视图不一样. 新建项目----ASP.NET MVC 4 Web 应用程序------选择模板(空).视图引擎(Razor ) 1.视图中 c# 代码  与 HT ...

  4. C#生成ZIP压缩包

    生成ZIP压缩包C#代码如下: using System; using System.Collections.Generic; using System.Text; using ICSharpCode ...

  5. MDK5 新建工程提示报错:Load PDSC Dubug Description faild

    File : D:\Keil_v5\ARM\PACK\Keil\STM32F0xx_DFP\2.0.0\Keil.STM32F0xx_DFP.pdsc Sequence : CheckID Conte ...

  6. Python十讲

    第一讲:从零开始学Python 第二讲:变量和基础数据类型 第三讲:条件分支以及循环 第四讲:列表与元组 第五讲:字典 第六讲:函数 第七讲:类 第八讲:标准库 第九讲:异常 第十讲:文件处理

  7. 1474 十进制转m进制

    1474 十进制转m进制  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 白银 Silver 题解       题目描述 Description 将十进制数n转换成m进制数 m ...

  8. 《数据库系统概念》11-扩展的E-R特性

    ​虽然基本的E-R特性可以应对大部分数据库建模,但为了使用方便,也提供扩展的E-R特性 一.特化Specialization有时一个实体集会保护若干子集,这些子集各自具有不同的属性.比如person可 ...

  9. js判断当前浏览器语言类型

    console.log(window.navigator.language.slice(0, 2)); 得到的是zh

  10. 喜闻乐见-Android应用的生命周期

    本文主要讲述了App的启动流程.Application的生命周期以及进程的回收机制. 在绝大多数情况下,每一个Android应用都在自己的Linux进程中运行.当需要运行某些代码时,进程就会被创建.进 ...