倍增实现LCA
Today,we will talk about how to find The Least Common Ancestor.
Now ,let us get into the business(正题).
为了大家可以更好地理解......
I will use chinese. 好吧,我装不下去了.
写在前面:
会有人问,不写最简单的暴力,一开始就写倍增实现,会不会有点太唐突。
我想说,其实二者思路几乎一致,后者只是前者稍微修改一下而已。明白
了倍增,暴力几乎对着打。
开始对LCA的讲解:
LCA是啥?吃?,LCA表示在树上两点之间的最近公共祖先。
以下图为例,手工画作可能会比较粗糙,凑合着看吧。
2 和 3的 LCA 是多少呢 ? 显然他们只有 1 一个祖先,所以1 没毛病。
那么 5 和 8 的 LCA是是多少呢? 8->4->2 , 5->2, 所以他的们的 LCA是 4.
那么如何实现LCA呢?
首先定义几个数组:
deep[ i ]数组表示第 i 个节点所在的深度层数。
up[ i ][ j ] , 注意是个二维数组,表示第 i 个节点向上 2j 层所到达的节点,,例如上图p[13][1]=4。
dis[ i ][ j ],也是二维哦,表示从第 i 个点向上 2j 层上的那个点的权值(也就是距离),若权值都唯一,可忽略。
当然你还需要连边建树:
这就不多bb了。
int head[maxn],tot;
void add(int x,int y)
{
edge[++tot].nxt=head[x],edge[tot].to=y;head[x]=tot; // 注意一下双向边
}
开始分析:
deep[ ]数组:
deep[x]=deep[ fa[x] ]+1,fa[x]表示x的父节点,这点很显然,没啥好说的。
up[ ][ ]数组 :
这个数组比较有意思,确切的说是 关于2的乘方 比较好玩了。
up[ x ][ i ] = up[ up[ x ][ i-1 ] ][i-1] (颜色区分下),
你可以理解为 2i = 2i-1 + 2i-1 。
有问题?那么我们可以一步一步探讨一下。
先看标记部分先跳到x向上 2i-1 个点,记为u点,那么再从u点向上 2j-1 层,则相对于x点来说不就可以看作 向上2 * 2i-1 =2i 层么。
还不理解的话,可以从图上手动演示下。
dis[ ][ ]数组:
dis[ i ][ j ] = dis[ i ][ j-1 ] + dis[ up[ i ][ j-1 ] ][ j-1 ].
若已经明白以上 up 数组 则明白这个性质了,在这不不做过多解释(不明白?按照上图手演)。
开始dfs预处理部分:
通过对以上数组的解释这部分几乎已经讲解完了。
看代码,可能会更好的帮你理解:
void dfs(int u,int fa) // u表示当前节点,fa表示其父节点。
{
deep[u]=deep[fa]+; p[u][]=fa; //这行没啥问题。
for(int i=;(<<i)<=deep[u];i++)p[u][i]=p[p[u][i-]][i-]; //深度跳跃,上边有解释。
for(int i=head[u];i;i=edge[i].nxt)
if(edge[i].to!=fa)dfs(edge[i].to,u); // if判断是否是节点的爸爸,因为他们之间有边啊
}
那么开始实现LCA了吧:
1.首先我们需要判断一下谁深谁浅,记所求LCA的两点位a,b。
这简单,
if(deep[a]>deep[b])swap(a,b);
确保a比b浅,也就是在树上,a点相比较于b点较高。
2.将两点转化为在同一深度。
那么a点比较高,那么就从b点开始往上跳吧。
先代码,后解释。
for(int i=;i>=;i--)
if(deep[a]<=deep[b]-(<<i))b=p[b][i];
注意这时候向上跳的i我们需要从大数开始枚举,20的话足够大了,想..220多少层了。
我们不允许b点跳的比a高,若b点向上跳,跳不超,那么就让b向上跳。
这里还是 2的乘方 的性质,循环过后,他两个一定在同一层(why? 小于2i的数绝对可以用x(x<i),相加得到 )。
3.特判一下(避免浪费时间)
if(a==b)return a;
4.一起往上跳
for(int i=;i>=;i--)
{
if(p[a][i]==p[b][i])continue;
else a=p[a][i],b=p[b][i];
}
跳完以后他们就在同一深度,并且a和b任意一个节点的父节点,为a和b的公共祖先,都是父节点了,当然是最近的咯。
这一部分代码:
int lca(int a,int b)
{
if(deep[a]>deep[b])swap(a,b);
for(int i=;i>=;i--)
if(deep[a]<=deep[b]-(<<i))b=p[b][i];
if(a==b)return a;
for(int i=;i>=;i--)
{
if(p[a][i]==p[b][i])continue;
else a=p[a][i],b=p[b][i];
}
return p[a][];
}
luogu P3379 【模板】最近公共祖先(LCA)
代码:
#include <iostream>
#include <cstring>
#include <cstdio>
#define maxn int(5e5+2)
using namespace std;
struct ahah{
int nxt,to;
}edge[maxn];
int n,m,s;
int head[maxn],tot;
void add(int x,int y)
{
edge[++tot].nxt=head[x],edge[tot].to=y;head[x]=tot;
}
int deep[maxn],p[maxn][];
void dfs(int u,int fa) // u表示当前节点,fa表示其父节点。
{
deep[u]=deep[fa]+; p[u][]=fa; //这行没啥问题。
for(int i=;(<<i)<=deep[u];i++)p[u][i]=p[p[u][i-]][i-]; //深度跳跃,上边有解释。
for(int i=head[u];i;i=edge[i].nxt)
if(edge[i].to!=fa)dfs(edge[i].to,u); // if判断是否是节点的爸爸,因为他们之间有边啊
}
int lca(int a,int b)
{
if(deep[a]>deep[b])swap(a,b);
for(int i=;i>=;i--)
if(deep[a]<=deep[b]-(<<i))b=p[b][i];
if(a==b)return a;
for(int i=;i>=;i--)
{
if(p[a][i]==p[b][i])continue;
else a=p[a][i],b=p[b][i];
}
return p[a][];
}
int main()
{
int a,b;
scanf("%d%d%d",&n,&m,&s);
for(int i=;i<n;i++)scanf("%d%d",&a,&b),add(a,b),add(b,a);
dfs(s,);
while(m--)
{
scanf("%d%d",&a,&b);
printf("%d\n",lca(a,b));
}
}
#include<stdio.h>
#include<string.h>
#include<vector>
#include<stdlib.h>
#include<math.h>
#define max 40001
#define maxl 25
using namespace std;
typedef struct
{
int from,to,w;
}edge;//这个结构体用来存储边 vector<edge>edges;
vector<int> G[max];
//保存边的数组
int grand[max][maxl],gw[max][maxl];//x向上跳2^i次方的节点,x到他上面祖先2^i次方的距离
int depth[max];//深度
int n,m,N;
void addedge(int x,int y,int w)//把边保存起来的函数
{
edge a={x,y,w},b={y,x,w};
edges.push_back(a);
edges.push_back(b); G[x].push_back(edges.size()-);
G[y].push_back(edges.size()-);
}
void dfs(int x)//dfs建图
{
for(int i=;i<=N;i++)//第一个几点就全部都是0咯,第二个节点就有变化了,不理解的话建议复制代码输出下这些数组 {
grand[x][i]=grand[grand[x][i-]][i-];
gw[x][i]=gw[x][i-]+gw[grand[x][i-]][i-];
// if(grand[x][i]==0) break;
} for(int i=;i<G[x].size();i++)
{ edge e = edges[G[x][i]];
if(e.to!=grand[x][])//这里我们保存的是双向边所以与他相连的边不是他父亲就是他儿子父亲的话就不能执行,不然就死循环了。 {
depth[e.to]=depth[x]+;//他儿子的深度等于他爸爸的加1
grand[e.to][]=x;//与x相连那个节点的父亲等于x
gw[e.to][]=e.w;//与x相连那个节点的距离等于这条边的距离
dfs(e.to);//深搜往下面建
}
}
}
void Init(){
//n为节点个数
N = floor(log(n + 0.0) / log(2.0));//最多能跳的2^i祖先
depth[]=;//根结点的祖先不存在,用-1表示
memset(grand,,sizeof(grand));
memset(gw,,sizeof(gw));
dfs();//以1为根节点建树 }
int lca(int a,int b)
{ if(depth[a] > depth[b]) swap(a, b);//保证a在b上面,便于计算
int ans = ;
for(int i = N; i >= ; i--) //类似于二进制拆分,从大到小尝试
if(depth[a] < depth[b] && depth[grand[b][i]] >= depth[a])//a在b下面且b向上跳后不会到a上面
ans +=gw[b][i], b=grand[b][i];//先把深度较大的b往上跳 for(int j=N;j>=;j--)//在同一高度了,他们一起向上跳,跳他们不相同节点,当全都跳完之后grand【a】【0】就是lca,上面有解释哈。
{
if(grand[a][j]!=grand[b][j])
{ ans+=gw[a][j];
ans+=gw[b][j];
a=grand[a][j];
b=grand[b][j]; }
}
if(a!=b)//a等于b的情况就是上面土色字体的那种情况
{
ans+=gw[a][],ans+=gw[b][];
}
return ans;
}
int main()
{ int t ;
scanf("%d",&t);
while(t--)
{ scanf("%d%d",&n,&m);
for(int i=;i<n;i++)
{
int x,y,w;
scanf("%d%d%d",&x,&y,&w);
addedge(x,y,w);
}
Init();
for(int i=;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
}
}
hdu 2586
倍增实现LCA的更多相关文章
- 树上倍增求LCA(最近公共祖先)
前几天做faebdc学长出的模拟题,第三题最后要倍增来优化,在学长的讲解下,尝试的学习和编了一下倍增求LCA(我能说我其他方法也大会吗?..) 倍增求LCA: father[i][j]表示节点i往上跳 ...
- [算法]树上倍增求LCA
LCA指的是最近公共祖先(Least Common Ancestors),如下图所示: 4和5的LCA就是2 那怎么求呢?最粗暴的方法就是先dfs一次,处理出每个点的深度 然后把深度更深的那一个点(4 ...
- Codeforces 519E A and B and Lecture Rooms [倍增法LCA]
题意: 给你一棵有n个节点的树,给你m次询问,查询给两个点,问树上有多少个点到这两个点的距离是相等的.树上所有边的边权是1. 思路: 很容易想到通过记录dep和找到lca来找到两个点之间的距离,然后分 ...
- 【倍增】洛谷P3379 倍增求LCA
题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每 ...
- hdu 2586 How far away ? 倍增求LCA
倍增求LCA LCA函数返回(u,v)两点的最近公共祖先 #include <bits/stdc++.h> using namespace std; *; struct node { in ...
- 倍增求lca模板
倍增求lca模板 https://www.luogu.org/problem/show?pid=3379 #include<cstdio> #include<iostream> ...
- 【题解】洛谷P4180 [BJWC2010] 严格次小生成树(最小生成树+倍增求LCA)
洛谷P4180:https://www.luogu.org/problemnew/show/P4180 前言 这可以说是本蒟蒻打过最长的代码了 思路 先求出此图中的最小生成树 权值为tot 我们称这棵 ...
- 【模板时间】◆模板·I◆ 倍增计算LCA
[模板·I]LCA(倍增版) 既然是一篇重点在于介绍.分析一个模板的Blog,作者将主要分析其原理,可能会比较无趣……(提供C++模板) 另外,给reader们介绍另外一篇非常不错的Blog(我就是从 ...
- 倍增求LCA学习笔记(洛谷 P3379 【模板】最近公共祖先(LCA))
倍增求\(LCA\) 倍增基础 从字面意思理解,倍增就是"成倍增长". 一般地,此处的增长并非线性地翻倍,而是在预处理时处理长度为\(2^n(n\in \mathbb{N}^+)\ ...
- 树链剖分与倍增求LCA
树链剖分与倍增求\(LCA\) 首先我要吐槽机房的辣基供电情况,我之前写了一上午,马上就要完成的时候突然停电,然后\(GG\)成了送链剖分 其次,我没歧视\(tarjan LCA\) 1.倍增求\(L ...
随机推荐
- 【WEB基础】HTML & CSS 基础入门(4)列表及其样式
前面 网页中漂亮的导航.整齐规范的文章标题列表和图片列表等等.这些都是离不开HTML里一个重要的元素----列表,在HTML中有无序列表.有序列表和定义列表三种类型.其中,无序列表应用最为广泛,下面, ...
- Linux--------------mysql的安装
工具: 1>Centos6.8 2>Jdk1.7 3>Mysql5.7 1.下载mysql wget http://dev.mysql.com/get/Download ...
- Gist使用经验
注:本文只是分享Gist使用经验,不讨论类似软件或服务的优劣,对于技术或软件不要有傲慢与偏见 一.Gist是什么 关于Gist的详细介绍,请阅读官方文档About gists,下面只简略介绍我所用到的 ...
- Swift dynamic关键字
使用dynamic关键字标记属性,使属性启用Objc的动态转发功能: dynamic只用于类,不能用于结构体和枚举,因为它们没有继承机制,而Objc的动态转发就是根据继承关系来实现转发. 参考资料: ...
- Hexo瞎折腾系列(7) - Coding Pages申请SSL/TLS证书错误
问题 今天我的个人站点SSL/TLS证书到期,我的证书是由Coding Pages提供的,每次申请成功后有效期是三个月,证书到期后可以继续免费申请.但是当我登陆进入Coding Pages服务的后台并 ...
- SpringBoot | contrller的使用
@Controller 处理http请求 @RestController Spring4之后新加的注解,原来返回json需要@ResponseBody配合@Controller @RequestMap ...
- HDU2586(tarjanLCA板子)
; int T, n, m; int f[maxn], vis[maxn], dis[maxn], ans[maxn]; vector<P> vc[maxn]; vector<int ...
- eclipse快捷键,移动和复制一段代码
移动代码:alt+上或下箭头 复制加移动代码:ctrl + alt + 上或下箭头
- 125 Valid Palindrome 验证回文字符串
给定一个字符串,确定它是否是回文,只考虑字母数字字符和忽略大小写.例如:"A man, a plan, a canal: Panama" 是回文字符串."race a c ...
- neo4j(图数据库)是什么?
不多说,直接上干货! 作为一款强健的,可伸缩的高性能数据库,Neo4j最适合完整的企业部署或者用于一个轻量级项目中完整服务器的一个子集存在. 它包括如下几个显著特点: 完整的ACID支持 高可用性 轻 ...