树上倍增求LCA及例题
先瞎扯几句
树上倍增的经典应用是求两个节点的LCA
当然它的作用不仅限于求LCA,还可以维护节点的很多信息
求LCA的方法除了倍增之外,还有树链剖分、离线tarjan ,这两种日后再讲(众人:其实是你不会吧:unamused:。。。)
思想
树上倍增嘛,顾名思义就是倍增
相信倍增大家都不默认,著名的rmq问题的O(n*logn)的解法就是利用倍增实现的
在树上倍增中,我们用
f[j][i]表示第j号节点,跳了2^j步所能到达的节点
deep[i]表示i号节点的深度
然后用这两个数组瞎搞搞就能整出LCA来啦
众人::wrench: :hammer: :hocho:
实现
deep&&f[i][0]
首先,f[i][0](也就是一个节点的上面的节点)容易求得,只要对整棵树进行一边dfs就好,在dfs的时候我们顺便可以求出deep数组
for(int i=head[now];i!=-1;i=edge[i].nxt)
if(!deep[edge[i].v])
deep[edge[i].v]=deep[now]+1,f[edge[i].v][0]=now,dfs(edge[i].v);
这段代码应该不难理解
f[j][i]
那么我们怎么维护f数组呢?
不难得到f[j][i]=f[f[j][i-1]][i-1] 众人:难!
其实真的不难,一张图就可以解释明白啦
这句话的意思其实是说,一个节点跳$2^j$所能到达的节点实际上是跳2^{i-1}所能到达的节点再往上跳2^{j-1}步
注意2^i=2^{i-1}+2^{i-1}
代码:
for(int i=1;i<=19;i++)
for(int j=1;j<=n;j++)
f[j][i]=f[f[j][i-1]][i-1];
LCA
接下来要进入最核心的部分啦,
我们如何用deep和f乱搞搞出x和y的LCA呢?
按照书上倍增算法的介绍
我们求LCA需要分为两步
设deep[x]>deep[y]
- 让x向上跳,跳到与y深度相同位置
- 让x和y同时向上跳,跳到祖先相同位置
根据二进制分解什么乱七八糟的,这么做一定是对的,其实这个挺显然的,yy一下就好了吧。。。
第一步
if(deep[x]<deep[y]) swap(x,y);
for(int i=19;i>=0;i--)
if(deep[f[x][i]]>=deep[y])
x=f[x][i];
首先处理一下x和y的深度,保证deep[x]>deep[y]
然后尽量让x向上跳就好啦,注意这里是可以取到等号的
注意这里可能会出现一种特殊情况
这个时候他们的最近公共祖先就是y
if(x==y) return x;
第二步
同时向上跳,直到祖先相同为止
那么此时他们再向上跳一步所能到达的节点就是LCA啦
for(int i=19;i>=0;i--)
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
怎么样?
是不是很简单?
完整代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=1000010;
inline void read(int &n)
{
char c=getchar();bool flag=0;n=0;
while(c<'0'||c>'9') c=='-'?flag=1,c=getchar():c=getchar();
while(c>='0'&&c<='9') n=n*10+c-48,c=getchar();flag==1?n=-n:n=n;
}
struct node
{
int v,nxt;
}edge[MAXN];
int head[MAXN];
int num=1;
inline void add_edge(int x,int y)
{
edge[num].v=y;
edge[num].nxt=head[x];
head[x]=num++;
}
int f[MAXN][21];
int deep[MAXN];
int n,m,root;
void dfs(int now)
{
for(int i=head[now];i!=-1;i=edge[i].nxt)
if(!deep[edge[i].v])
deep[edge[i].v]=deep[now]+1,f[edge[i].v][0]=now,dfs(edge[i].v);
}
void PRE()
{
for(int i=1;i<=19;i++)
for(int j=1;j<=n;j++)
f[j][i]=f[f[j][i-1]][i-1];
}
int LCA(int x,int y)
{
if(deep[x]<deep[y]) swap(x,y);
for(int i=19;i>=0;i--)
if(deep[f[x][i]]>=deep[y])
x=f[x][i];
if(x==y) return x;
for(int i=19;i>=0;i--)
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
}
int main()
{
memset(head,-1,sizeof(head));
read(n);read(m);read(root);
for(int i=1;i<=n-1;i++)
{
int x,y;read(x);read(y);
add_edge(x,y);
add_edge(y,x);
}
deep[root]=1;
dfs(root);
PRE();
for(int i=1;i<=m;i++)
{
int x,y;
read(x);read(y);
printf("%dn",LCA(x,y));
}
return 0;
}
例题
都是些入门难度的题目
洛谷P3379 【模板】最近公共祖先(LCA)
POJ 1986 Distance Queries
http://www.cnblogs.com/zwfymqz/p/7791527.html
HDU 3078 Network
http://www.cnblogs.com/zwfymqz/p/7791617.html
HDU 2586 How far away ?
http://www.cnblogs.com/zwfymqz/p/7791517.html
- 统计01:概述
- ActiveMQ笔记(2):基于ZooKeeper的HA方案
- ActiveMQ笔记(2):基于ZooKeeper的HA方案
- CSS几个竖直与水平居中盒子模型
- ZooKeeper 笔记(6) 分布式锁
- Hadoop(十五)MapReduce程序实例
- dubbox 的各种管理和监管
- bash魔法堂:History用法详解
- 大众点评cat系统的搭建笔记
- 我们只能在安全和隐私之间寻求平衡吗?
- Docker Compose 1.18.0 之服务编排详解
- 让IE7/8使用CSS中first-child和last-child样式属性
- dubbo/dubbox 增加原生thrift及avro支持
- Hadoop(十四)MapReduce原理分析
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法