倍增法求lca(最近公共祖先)

2024-05-01 17:58

本文主要是介绍倍增法求lca(最近公共祖先),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

思路:

大致上算法的思路是这样发展来的。

想到求两个结点的最小公共祖先,我们可以先把两个的深度提到同一水平,在一步一步往上跳,直到两个结点有了一个公共祖先,依照算法流程,这就是least common ancestor。

但是如果这样一步步地往上未免太让人着急,为了提高一下效率,便不再每次只跳一步,而跳2i

步。一般的,先这样蹦蹦跳跳跳上去直到两个结点相平,在两个一起这样蹦上去。

怎么确定这个i该是多少合适呢?

这里我们需要预处理一个数组f,f[u] [i]来表示结点u的第i代祖先,f[u] [0]表示结点u的父亲,这个数组用链式前向星(最近怎么老是它)加上dfs来预处理产生,十分便捷。那它有什么用呢?用在提升结点的时候,我们可以借助这个数组直接将结点提到他的合适的祖先上去。怎么才算合适的祖先?现假设求lca(s,t)

分两步,第一步是将低结点提到高结点(相对于叶节点)的深度上。这时候一个for循环从s的第20代祖先开始(为什么是二十代?可能一般的树达不到这个深度),看能不能把s提上去后满足depth[s]>=depth[t]

,不能满足就看第十九代祖先这样一直减少i,若满足了就把s提上去,还是继续减少i,直到两个结点最终相平。这个过程有个博客里讲的很形象(见参考文章),比喻成乌鸦喝水的过程,就是乌鸦先把体积大的(这里就是i值)放进水杯里,再逐渐减小放入物品的体积直到把水杯的水升上来,而不是先填沙子这种小颗粒的东西。这就是为什么i值要从大到小。

第二步是整体提升的过程,我们不知道该几步提升,在循环中的条件就变成了if(f[s][i]!=f[t][i])

。也就是只要他两的祖先不一样就提升,祖先一样有两种可能,一种可能是节点是祖先但不是最小公共祖先,另一种可能是到了公共祖先。前一种出现在循环的开始,一开始想要提的深度比较多,所以很可能提到了公共祖先。这种情况不用管,继续减小i值,之后可能会由于满足if条件而经历一些两点提升的过程,最后不满足if

条件了,说明两个已经有了最小公共祖先,这时候随意输出一个节点的父结点就行了。

算法完成,看看代码。如觉得不太清楚请看参考文章。

代码:

#include <iostream>
#include <cstdio>
#define max_n 500005
using namespace std;
int n,m,s;//n为结点数,m为边数,s为根节点标号
int lg[max_n];//优化用到的预处理的数组,存log_2[i]-1
int f[max_n][23];//f[u][i]表示结点u的第i代祖先,其中f[u][0]为u的父结点
int depth[max_n];//节点深度
//链式前向星
int head[max_n];
struct edge
{int v;int next;
}e[max_n<<1];
int cnt = 0;
void add(int u,int v)
{++cnt;e[cnt].v = v;e[cnt].next = head[u];head[u] = cnt;
}
//快速读入模板
inline void read(int& x)
{x=0;int f=0;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f = 1;ch = getchar();}while('0'<=ch&&ch<='9') {x = 10*x+ch-'0';ch=getchar();}x = f?-x:x;
}
//dfs预处理出结点往上跳2^i的结点
void dfs(int u,int from)
{depth[u] = depth[from]+1;//比父结点深度加一for(int i = 1;1<<i<=depth[u];i++)//祖先结点要存在,不存在的默认为零{f[u][i] = f[f[u][i-1]][i-1];//f[u][i]的第u个结点第i-1代祖先的第i-1代祖先记为第i代祖先}for(int i = head[u];i;i=e[i].next){int v = e[i].v;if(v==from) continue;//因为是无向边,判断不能反回父结点f[v][0] = u;//dfs(v,u);}
}
//求最近公共祖先
int lca(int s,int t)
{if(depth[s]<depth[t]) swap(s,t);//设s比t深while(depth[s]>depth[t])//若s比t深,不断上移s{s = f[s][lg[depth[s]-depth[t]]-1];//上移log_2[深度差]步,直到相平}if(s==t)//若t为s的祖先{return s;//则s,t的lca是t}for(int i = lg[depth[s]]-1;i>=0;i--)//待s,t相平后{if(f[s][i]!=f[t][i])//只要两公共祖先不等{s = f[s][i];//将二者上移t = f[t][i];}}/*for(int i = 20;i>=0;i--){if(f[s][i]!=f[t][i]){s = f[s][i];t = f[t][i];}}*/return f[s][0];//直到最后两者公共祖先相等,记为lca
}
int main()
{read(n);read(m);read(s);for(int i = 1;i<=n;i++)//优化数组玄学构造法求log_2[i]+1{lg[i] = lg[i-1]+((1<<lg[i-1])==i);}for(int i = 1;i<n;i++){int u,v;read(u);read(v);add(u,v);add(v,u);}dfs(s,0);int ans = 0;for(int i = 1;i<=m;i++){int a,b;read(a);read(b);ans = lca(a,b);cout << ans << endl;}return 0;
}

这篇关于倍增法求lca(最近公共祖先)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/952318

相关文章

poj1330(LCA最近公共祖先)

题意:求最近公共祖先 思路:之前学习了树链剖分,然后我就用树链剖分的一小部分知识就可以解这个题目了,记录每个结点的fa和depth。然后查找时,每次将depth大的结点往上走直到x = y。 代码如下: #include<iostream>#include<algorithm>#include<stdio.h>#include<math.h>#include<cstring>

最近心情有点复杂:论心态

一月一次的彷徨又占据了整个身心;彷徨源至不自信;而不自信则是感觉自己的价值没有很好的实现亦或者说是自己不认可自己的目前的生活和状态吧。 我始终相信一句话:任何人的生活形态完全是由自己决定的;外在的总归不能直达一个人的内心深处。所以少年 为了自己想要的生活 多坚持努力吧、不为别人只为自己心中的那一丝执着。 由此我看到了一个故事: 一个心情烦躁的人去拜访禅师。他问禅师:我这辈子就这么注定了吗?您

在二叉树中找到两个节点的最近公共祖先(基于Java)

如题  题解 public int lowestCommonAncestor(TreeNode root, int o1, int o2) {//记录遍历到的每个节点的父节点。Map<Integer, Integer> parent = new HashMap<>();Queue<TreeNode> queue = new LinkedList<>();parent.put(roo

【UVA】10066-The Twin Towers(最长公共子串问题)

赤裸裸的最长公共子串问题,没什么好说的,注意的是,每组数据后面都有一个空行。 13996019 10066 The Twin Towers Accepted C++ 0.015 2014-08-06 00:34:53 #include<cstdio>#include<cstring>#include<iostream>#include<algorithm>using

如何限制与管控员工上网行为?四个方法让员工效率倍增!【企业员工上网行为管理】

在信息化时代,员工的上网行为直接影响着工作效率和企业的安全性。不当的网络使用,如浏览与工作无关的网站、下载不安全的文件,可能导致工作效率低下,甚至引发安全风险。因此,许多企业正在积极寻找有效的措施来管控员工的上网行为,以确保工作效率的提升。 以下是四个常见且有效的员工上网行为管理方法,帮助企业实现更高效的网络管理。 方法一:配置网络防火墙进行访问限制 最基础的员工上网行为管理方法是通过配置防

算法图解(8~10贪心,动态规划,K最近邻算法)

贪心算法 在每一步都选择局部最优解,从而期望最终得到全局最优解。 贪心算法并不总能保证全局最优解,因此需要满足以下两个条件: 贪心选择性质:可以通过局部最优选择构造出全局最优解。最优子结构:问题的最优解包含其子问题的最优解。 实例:给定面额的硬币,用最少硬币凑出指定金额 int minCoins(vector<int>& coins, int amount) {int count = 0

救命!我已经彻底被最近的FLUX模型征服了

这是一期FLUX模型配套的罗拉模型推荐,这个大模型真的很香,尤其是对于人物的手部处理,推荐大家去尝试下,我已经爱上这个大模型了。 ① Flux魅影超模 https://www.liblib.art/modelinfo/15818662ba2e443d9b4f9a87c13fff55 关键词:照片上是一位优雅的年轻女子,一头棕色卷发,身穿米色上衣,戴着金耳环。她背对着相机,背景是浅色的。重点是

计算两个字符串的最大公共字符串的长度,字符不区分大小写

/*** */package testString;import java.util.Scanner;/***@author: Administrator*@date: 2016-12-28 下午01:08:30*/public class Main {public static void main(String[] args){Scanner sc=new Scanner(Syste

redis 实现单位时间内错误记录 时间到key值就被清除------最近脑子不好使觉得还是写个博客试试

直接在客户端操作的, 所以需要redis的简单命令  去对比JAVA客户端jedis的命令就行   添加---set     格式 set  key  value  EX time(秒)   如果这个time不添加的话 ,那默认就是 永久 获取--get    格式 get key  ---查看剩余时间    格式 TTL key ---实现key实现自增: inrc key

最近刚接触用到的一些linux命令(CentOS7的命令)二〇一八年十月三十日

linux  查看本地     ip  ip addr  查看本地系统     #cat /etc/issue 在CentOS下执行显示为: CentOS release 5.7 (Final) Kernel \r on an \m 或在Ubuntu下显示为: Ubuntu 11.04 \n \l 可以用来查看当前正在运行的 Ubuntu 的版本号。  查看系统内核     uname -a