最近公共祖先(LCA)模板
以下转自:https://www.cnblogs.com/JVxie/p/4854719.html
首先是最近公共祖先的概念(什么是最近公共祖先?):
在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先,就是两个节点在这棵树上深度最大的公共的祖先节点。
换句话说,就是两个点在这棵树上距离最近的公共祖先节点。
所以LCA主要是用来处理当两个点仅有唯一一条确定的最短路径时的路径。
有人可能会问:那他本身或者其父亲节点是否可以作为祖先节点呢?
答案是肯定的,很简单,按照人的亲戚观念来说,你的父亲也是你的祖先,而LCA还可以将自己视为祖先节点。
举个例子吧,如下图所示4和5的最近公共祖先是2,5和3的最近公共祖先是1,2和1的最近公共祖先是1。 
这就是最近公共祖先的基本概念了,那么我们该如何去求这个最近公共祖先呢?
通常初学者都会想到最简单粗暴的一个办法:对于每个询问,遍历所有的点,时间复杂度为O(n*q),很明显,n和q一般不会很小。
常用的求LCA的算法有:Tarjan(离线)、倍增法(在线)
下面贴两种写法的代码,以HDU 2586 How far away ?为例:
题目大意:
给你一棵树n个节点,m次询问,每次询问树上节点u和v的最小距离。
Tarjan(时间复杂度O(n+Qlogn))
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N=4e4+; struct node{
int to,w;
node(int to,int w):to(to),w(w){}
}; struct qnode{
int to,id;
qnode(int to,int id):to(to),id(id){}
}; vector<node>v[N];
vector<qnode>q[N];
bool vis[N]; //vis[i]表示点i是否被访问过
int res[N],root[N],dis[N]; //res记录答案 int find(int x){
return root[x]==x?x:root[x]=find(root[x]);
} void lca(int u){
vis[u]=true;
for(int i=;i<v[u].size();i++){
node t=v[u][i];
if(!vis[t.to]){
dis[t.to]=dis[u]+t.w;
lca(t.to);
root[t.to]=u;
}
}
for(int i=;i<q[u].size();i++){
qnode t=q[u][i];
if(vis[t.to]){
res[t.id]=dis[u]+dis[t.to]-*dis[find(t.to)];
}
}
} int main(){
int t;
scanf("%d",&t);
while(t--){
int n,m;
scanf("%d%d",&n,&m);
memset(vis,false,sizeof(vis));
memset(dis,,sizeof(dis));
for(int i=;i<=n;i++){
root[i]=i;
v[i].clear();
}
for(int i=;i<=m;i++){
q[i].clear();
}
for(int i=;i<=n-;i++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
v[a].push_back(node(b,c));
v[b].push_back(node(a,c));
}
for(int i=;i<=m;i++){
int a,b;
scanf("%d%d",&a,&b);
q[a].push_back(qnode(b,i));
q[b].push_back(qnode(a,i));
}
lca(1);
for(int i=;i<=m;i++){
printf("%d\n",res[i]);
}
}
return ;
}
倍增法(时间复杂度O(nlogn+Qlogn))
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int N=1e5+; struct node{
int to,w;
node(int to,int w):to(to),w(w){}
};
vector<node>v[N]; int n,m;
int depth[N],fa[N][],dis[N];
bool vis[N]; void init(){
memset(depth,,sizeof(depth));
memset(fa,,sizeof(fa));
memset(dis,,sizeof(dis));
memset(vis,false,sizeof(vis));
for(int i=;i<=n;i++) v[i].clear();
} void dfs(int u){
vis[u]=true;
for(int i=;i<v[u].size();i++){
node t=v[u][i];
if(!vis[t.to]){
depth[t.to]=depth[u]+;
fa[t.to][]=u;
dis[t.to]=dis[u]+t.w;
dfs(t.to);
}
}
} //倍增,处理fa数组
void bz(){
for(int j=;j<=;j++){
for(int i=;i<=n;i++){
fa[i][j]=fa[fa[i][j-]][j-];
}
}
} int lca(int x,int y){
//保证深度大的点为x
if(depth[x]<depth[y])
swap(x,y);
int dc=depth[x]-depth[y];
for(int i=;i<;i++){
if(<<i&dc) //一个判断,模拟下就会清楚
x=fa[x][i];
}
if(x==y) return x; //如果深度一样,两个点相同,直接返回
for(int i=;i>=;i--){
if(fa[x][i]!=fa[y][i]){ //跳2^i不一样,就跳,否则不跳
x=fa[x][i];
y=fa[y][i];
}
}
x=fa[x][];
return x;
} int main(){
int t;
scanf("%d",&t);
while(t--){
init();
scanf("%d%d",&n,&m);
for(int i=;i<=n-;i++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
v[a].push_back(node(b,c));
v[b].push_back(node(a,c));
}
dfs();
bz();
for(int i=;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
printf("%d\n",dis[x]+dis[y]-*dis[lca(x,y)]);
}
}
return ;
}
相关题目了解一下(转自这里):
hdu 2586 How far away ?
模板题,直接求LCA,可以在线,离线算法均可解,可以测试一下模板
poj 1986 Distance Queries
模板题,直接求LCA
hdu 2874 Connections between cities
模板题,不过不是树是森林,所以某些点不存在LCA,要做判断
zoj 3195 Design the city
任然算是模板题,上面的题要求两点间的最短距离,这里要求3点间的最短距离,其实就是两两之间求一次LCA并计算出距离,然后相加除以2即可
hdu 3078 Network
LCA + 修改点权值 + 排序:每个点有初始的权值,一边查询一边伴随着修改某些点的权值,查询是从a到b路径中第k大的权值是多少。不需要太多的技巧,修改操作就直接修改,查询操作先求LCA,然后从a走到b保存下所有的权值,排序,然后直接输出即可
poj 2763 Housewife Wind
LCA + 修改边权:一边查询两点间的距离,一边修改某些边权。对于修改了某些边的边权,就要从此开始遍历下面的子孙后代更改他们的dir值(点到根的距离)。也不需要太多技巧,直接按题意实现即可,不过时间比较糟糕,用线段树或树状数组可以对修改操作进行优化,时间提升很多
poj 3694 Network
连通分量 + LCA : 先缩点,再求LCA,并且不断更新,这题用了朴素方法来找LCA,并且在路径上做了一些操作
poj 3417 Network
LCA + Tree DP : 在运行Tarjan处理完所有的LCA询问后,进行一次树DP,树DP不难,但是要想到用树DP并和这题结合还是有点难度
poj 3728 The merchant
LCA + 并查集的变形,优化:好题,难题,思维和代码实现都很有难度,需要很好地理解Tarjan算法中并查集的本质然后灵活变形,需要记录很多信息(有点dp的感觉)
hdu 3830 Checkers
LCA + 二分:好题,有一定思维难度。先建立出二叉树模型,然后将要查询的两个点调整到深度一致,然后二分LCA所在的深度,然后检验
最近公共祖先(LCA)模板的更多相关文章
- 最近公共祖先lca模板
void dfs(int x,int root){//预处理fa和dep数组 fa[x][0]=root; dep[x]=dep[root]+1; for(int i=1;(1<<i)&l ...
- [模板] 最近公共祖先/lca
简介 最近公共祖先 \(lca(a,b)\) 指的是a到根的路径和b到n的路径的深度最大的公共点. 定理. 以 \(r\) 为根的树上的路径 \((a,b) = (r,a) + (r,b) - 2 * ...
- Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集)
Luogu 2245 星际导航(最小生成树,最近公共祖先LCA,并查集) Description sideman做好了回到Gliese 星球的硬件准备,但是sideman的导航系统还没有完全设计好.为 ...
- POJ 1470 Closest Common Ancestors(最近公共祖先 LCA)
POJ 1470 Closest Common Ancestors(最近公共祖先 LCA) Description Write a program that takes as input a root ...
- POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA)
POJ 1330 Nearest Common Ancestors / UVALive 2525 Nearest Common Ancestors (最近公共祖先LCA) Description A ...
- 【lhyaaa】最近公共祖先LCA——倍增!!!
高级的算法——倍增!!! 根据LCA的定义,我们可以知道假如有两个节点x和y,则LCA(x,y)是 x 到根的路 径与 y 到根的路径的交汇点,同时也是 x 和 y 之间所有路径中深度最小的节 点,所 ...
- lca最近公共祖先(模板)
洛谷上的lca模板题--传送门 学了求lca的tarjan算法(离线),在洛谷上做模板题,结果后三个点超时. 又把询问改成链式前向星,才ok. 这个博客,tarjan分析的很详细. 附代码-- #in ...
- LCA最近公共祖先——Tarjan模板
LCA(Lowest Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先. Tarjan是一种离线算法,时间复杂度O(n+Q),Q表示询问次数,其中 ...
- luogu3379 【模板】最近公共祖先(LCA) 倍增法
题目大意:给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 整体步骤:1.使两个点深度相同:2.使两个点相同. 这两个步骤都可用倍增法进行优化.定义每个节点的Elder[i]为该节点的2^k( ...
随机推荐
- MySQL事务及隔离级别(读书小结)
标签: MySQL事务 隔离 0.什么是事务? 事务是指MySQL的一些操作看做是一个不可分割的执行单元.事务的特点是要么所有操作都执行成功,要么一个都不执行.也就是如果一个事务有操作执行失败,那么就 ...
- bzoj3533【Sdoi2014】向量集
题目描述 维护一个向量集合,在线支持以下操作:"A x y (|x|,|y| < =10^8)":加入向量(x,y);" Q x y l r (|x|,|y| &l ...
- 【agc001d】Arrays and Palindrome
Portal -->agc001D Description 给你一个\(m\)个数的排列\(A\),这个\(A\)中元素的顺序可以随便调换,\(A\)中的元素的和为\(n\),现在要你构造一个数 ...
- 850. 矩形面积 II
我们给出了一个(轴对齐的)矩形列表 rectangles . 对于 rectangle[i] = [x1, y1, x2, y2],其中(x1,y1)是矩形 i 左下角的坐标,(x2,y2)是该矩形右 ...
- 省选模拟赛 cti
3 cti (cti.cpp/in/out, 1s, 512MB)3.1 Description有一个 n × m 的地图, 地图上的每一个位置可以是空地, 炮塔或是敌人. 你需要操纵炮塔消灭敌人.对 ...
- Java入门:Java环境变量PATH、CLASSPATH、JAVA_HOME
一些初学者在用java HelloWorld指令运行程序的时候出现: Exception in thread "main" java.lang.NoClassDefFoundErr ...
- 抓包工具Charles
Charles Charles可以在windows,linux,mac各种操作系统上安装使用,它是java编写一款非免费工具:而fiddler只能在windows系统上使用 Charles抓包前,要确 ...
- samba和nginx服务
samba和nginx服务 1.s配置amba samba的功能: samba是一个网络服务器,用于Linux和Windows之间共享文件. 1.1配置环境 关闭防火墙和selinux systemc ...
- python数据结构(1)
四种内建的数据结构:List.Tuple.Dictionary以及Set. 1.列表 列表是可变的,这是它区别于字符串和元组的最重要的特点,一句话概括即:列表可以修改,而字符串和元组不能. (1).创 ...
- Shell记录-Shell脚本基础(四)
while循环,使您能够重复执行一组命令,直到某些条件发生.它通常用于当你需要反复操纵的变量值. 语法 while command do Statement(s) to be executed if ...