最近公共祖先

news2024/9/29 5:35:39

最近公共祖先(Lowest Common Ancestor,LCA)
指两个点的公共祖先中,离根最远/深度最深的

性质:
1. L C A ( { u } ) = u LCA\left(\left\{u\right\}\right) = u LCA({u})=u
2.若 u u u v v v的祖先,当且仅当 L C A ( u , v ) = u LCA\left(u,v\right) = u LCA(u,v)=u
3.如果 u u u不是 v v v的祖先, v v v不是 u u u的祖先,则 u , v u,v u,v分别处于 L C A ( u , v ) LCA\left(u,v\right) LCA(u,v)的两棵不同的子树中
4.两个点的LCA必定出现在两点间的最短路上
5.设 d ( u , v ) d\left(u,v\right) d(u,v) u , v u,v u,v之间的距离, h ( u ) h\left(u\right) h(u) u u u到根的距离,则 d ( u , v ) = h ( u ) + h ( v ) − 2 h ( L C A ( u , v ) ) d\left(u,v\right) = h\left(u\right)+h\left(v\right) - 2h\left(LCA\left(u,v\right)\right) d(u,v)=h(u)+h(v)2h(LCA(u,v))

洛谷P3379

朴素

一步步往上跳

倍增

f a x , i \mathop{fa}_{x,i} fax,i表示 x x x 2 i 2^i 2i个祖先,用dfs预处理
查询 x , y x,y x,y的LCA时,先跳到同一个深度
然后按2的幂次从大到小跳,比如 5 = 101 b 5 = 101b 5=101b先跳 2 2 2^2 22,再跳 2 0 2^0 20
不过这么跳可能会跳过了,所以我们跳到LCA的子节点

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;

const int N = 500005;
const int M = 21;
vector<int> edge[N];

int logn[N] = { -1, 0 };//log2 n

int fa[N][M];//fa[i][j]表示i的2^j祖先
int depth[N];//深度

void dfs(int now, int father) {
	fa[now][0] = father;
	depth[now] = depth[father] + 1;
	for (int i = 1; i <= logn[depth[now]]; ++i){
		fa[now][i] = fa[fa[now][i - 1]][i - 1];
	}
	for (int i = 0; i < edge[now].size(); ++i) {
		if (edge[now][i] != father) {
			dfs(edge[now][i], now);
		}
	}
}

int lca(int x, int y) {
	if (depth[x] < depth[y])swap(x, y);
	//跳同一个深度
	while (depth[x] > depth[y]) {
		x = fa[x][logn[depth[x] - depth[y]]];
	}
	if (x == y)return x;
	//跳到LCA的子节点
	for (int i = logn[depth[x]]; i >= 0; --i) {
		if (fa[x][i] != fa[y][i]) {
			x = fa[x][i];
			y = fa[y][i];
		}
	}
	return fa[x][0];
}


int main() {
	int n, m, s, x, y;
	scanf("%d%d%d", &n, &m, &s);
	for (int i = 1; i < n; ++i) {
		scanf("%d%d", &x, &y);
		//双向边,以为不知道哪个在上面
		edge[x].push_back(y);
		edge[y].push_back(x);
	}
	for (int i = 2; i <= n; ++i)logn[i] = logn[i >> 1] + 1;
	dfs(s, 0);
	while (m--) {
		scanf("%d%d", &x, &y);
		printf("%d\n", lca(x, y));
	}
	return 0;
}

欧拉序-RMQ

欧拉序:其实就是dfs遍历的顺序(要记录出入)
E [ i ] E[i] E[i]为欧拉序中第 i i i个节点
p o s [ i ] pos[i] pos[i]为节点 i i i在欧拉序中第一次出现的索引

不妨假设 p o s ( u ) < p o s ( v ) pos\left(u\right)< pos\left(v\right) pos(u)<pos(v),则
p o s ( L C A ( u , v ) ) = min ⁡ { p o s ( k ) ∣ k ∈ E [ p o s ( u ) ⋯ p o s ( v ) ] } pos\left(LCA\left(u,v\right)\right) = \min\left\{pos\left(k\right)|k\in E\left[pos\left(u\right)\cdots pos\left(v\right)\right]\right\} pos(LCA(u,v))=min{pos(k)kE[pos(u)pos(v)]}

举个例子
在这里插入图片描述

node12345
pos42517
idx123456789
euler424131514
pos121454741

加粗表示第一次出现
比如 2 , 5 2,5 2,5,欧拉序索引为 2 , 7 2,7 2,7,欧拉序中 2 , 3 , ⋯   , 7 2,3,\cdots, 7 2,3,,7 p o s pos pos最小的是4

所以现在问题就是区间最小值,可以用st表

#include<cstdio>
#include<vector>
#include<algorithm>

using namespace std;

const int N = 500005;
const int M = 21;

vector<int> edge[N];

int logn[N << 1] = { -1, 0 };
int dfsn[N << 1], tot;//欧拉序
int pos[N];//节点i在欧拉序中第一次出现的位置
int st[N << 1][M];

void dfs(int now) {
	dfsn[++tot] = now;
	pos[now] = tot;
	for (int i = 0; i < edge[now].size(); ++i) {
		if (!pos[edge[now][i]]) {
			dfs(edge[now][i]);
			dfsn[++tot] = now;
		}
	}
}

void init_st() {
	for (int i = 1; i <= tot; ++i) {
		st[i][0] = dfsn[i];
	}
	
	for (int j = 1; j <= logn[tot]; ++j) {
		for (int i = 1; i + (1 << j) - 1 <= tot; ++i) {
			if (pos[st[i][j - 1]] < pos[st[i + (1 << (j - 1))][j - 1]]) {
				st[i][j] = st[i][j - 1];
			}
			else {
				st[i][j] = st[i + (1 << (j - 1))][j - 1];
			}
		}
	}
}

int main() {
	int n, m, s, x, y;
	scanf("%d%d%d", &n, &m, &s);

	for (int i = 1; i < n; ++i) {
		scanf("%d%d", &x, &y);
		edge[x].push_back(y);
		edge[y].push_back(x);
	}

	dfs(s);
	for (int i = 2; i <= tot; ++i)logn[i] = logn[i >> 1] + 1;
	init_st();
	while (m--) {
		scanf("%d%d", &x, &y);
		x = pos[x];
		y = pos[y];
		if (x > y)swap(x, y);
		int s = logn[y - x + 1];
		if (pos[st[x][s]] < pos[st[y - (1 << s) + 1][s]]) {
			printf("%d\n", st[x][s]);
		}
		else {
			printf("%d\n", st[y - (1 << s) + 1][s]);
		}
	}
	return 0;
}

Tarjan

tarjan算法是一种离线的算法,使用并查集记录节点的祖先

dfs,当访问完v时,如果有查询是 ( u , v ) (u,v) (u,v),则他们的LCA为并查集中 u u u的祖先
如下图
在这里插入图片描述

#include<cstdio>
#include<vector>
using namespace std;

const int N = 500005;
const int M = 500005;

vector<int> edge[N];
vector<pair<int, int> > query[M];//query[u] = (v,n) 表示第n个查询为(u,v)
int ans[M];//查询答案

bool visit[N];//访问
int parent[N];//并查集


int Find(int x) {
	int p = x;
	while (p != parent[p]) {
		p = parent[p];
	}

	while (x != p) {
		int y = parent[x];
		parent[x] = p;
		x = y;
	}
	return p;
}

void tarjan(int now) {
	parent[now] = now;
	visit[now] = true;
	for (int i = 0; i < edge[now].size(); ++i) {
		if (!visit[edge[now][i]]) {
			tarjan(edge[now][i]);
			
			parent[edge[now][i]] = now;
		}
	}

	for (int i = 0; i < query[now].size(); ++i) {
		int v = query[now][i].first;
		if (visit[v]) {
			ans[query[now][i].second >> 1] = Find(v);
		}
	}
}

int main() {
	int n, m, s, x, y;
	scanf("%d%d%d", &n, &m, &s);
	for (int i = 1; i < n; ++i) {
		scanf("%d%d", &x, &y);
		edge[x].push_back(y);
		edge[y].push_back(x);
	}

	for (int i = 0; i < m; ++i) {
		scanf("%d%d", &x, &y);
		query[x].push_back(make_pair(y, i << 1));
		query[y].push_back(make_pair(x, i << 1 | 1));
	}
	tarjan(s);
	for (int i = 0; i < m; ++i) {
		printf("%d\n", ans[i]);
	}
	return 0;
}

参考:
https://en.wikipedia.org/wiki/Lowest_common_ancestor

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/186408.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[Lua实战]Skynet-1.如何启动(linux环境启动)[开箱可用]

Skynet-如何启动1.依赖环境:可登录&联网的linux(Centos7)系统(可以是虚拟机)2.yum安装依赖库3.git clone skynet项目4.编译skynet4.1有可能遇到的错误(升级gcc到4.9以上即可解决):5.测试运行skynet6.运行结果最近用到了lua,想了解下云风大神的skynet,在网上看了半天也没入门…

Spire.Pdf for Java v9.1.4 Patcher

Spire.PDF for Java是一种 PDF API&#xff0c;它使 Java 应用程序无需使用第三方SDK 即可读取、写入和保存 PDF 文档。使用这个 Java PDF 组件&#xff0c;开发人员和程序员可以实现丰富的功能&#xff0c;从头开始创建 PDF 文件或完全在 Java 应用程序&#xff08;J2SE 和 J2…

leetcode刷题记录总结-7.二叉树

文章目录零、二叉树理论二叉树的种类满二叉树完全二叉树二叉搜索树平衡二叉搜索树二叉树的存储方式二叉树的遍历方式二叉树的定义总结一、二叉树的遍历[144. 二叉树的前序遍历 ](https://leetcode.cn/problems/binary-tree-preorder-traversal/)题解递归实现迭代实现[94. 二叉树…

如何运营企业网站

企业网站的最终目的是给企业带来效益&#xff0c;树立企业形象。只要有这个目标定位&#xff0c;剩下的工作就是围绕这个定位去做和优化&#xff0c;米贸搜整理如下&#xff1a;1.增强被收录页面的重要性。收录页面的提升不仅仅是数量的提升&#xff0c;质量占据了很高的比重。…

网络安全协议

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 前言 本章将会进行网络安全协议的讲解 一.网络安全 1.什么是网络安全 网络安全&#xff…

导数的概念——“高等数学”

各位CSDN的uu们你们好呀&#xff0c;今天小雅兰的内容是导数的概念&#xff0c;其实在高中时期&#xff0c;我们就已经接触过导数了&#xff0c;但是那个时候学得并不是特别深入&#xff0c;依稀记得&#xff0c;我们当初的导数大题一般都是压轴题&#xff0c;很多学校每次讲解…

Oracle重写sql经典50题(代码)

上次发表的是写的时候遇到的问题&#xff0c;这次备份一下自己的代码&#xff08;很冗余,也不保证正确率&#xff09; mysql50题链接在此 Oracle重写sql经典50题创建数据库和表oracle数据库一个账户就一个库&#xff0c;不需要创建学生表 student创建学生表 student插入学生数据…

A-Star算法探索和实现(五)

本篇摘要在上一篇中我们对寻路的移动规则进行了制定&#xff0c;而在本篇我们将对最佳路径的查找方式进行优化&#xff0c;而这就会涉及到移动规则的检测改进、权值计算的改进、NextNode集的处理改进、寻路逻辑的改进&#xff0c;我们将从上述四个方面进行详细讲解。方案探讨&a…

3.堆排序和比较器

1. 堆 堆结构就是用数组实现的完全二叉树结构&#xff0c;对于结点i&#xff0c;左孩子2*i1、右孩子2*i2、父节点&#xff08;i-1&#xff09;/ 2。 完全二叉树中如果每棵子树的最大值都在顶部就是大根堆 完全二叉树中如果每棵子树的最小值都在顶部就是小根堆堆结构的heapInse…

【C++算法图解专栏】一篇文章带你入门二分算法

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4e3;专栏定位&#xff1a;为 0 基础刚入门数据结构与算法的小伙伴提供详细的讲解&#xff0c;也欢迎大佬们一起交流~ &#x1f4da;专栏地址&#xff1a;https://blog.csdn.net/Newin…

步进式PID控制算法及仿真

在较大阶跃响应时&#xff0c;很容易产生超调。采用步进式积分分离PID控制&#xff0c;该方法不直接对阶跃信号进行响应&#xff0c;而是使输入指令信号一步一步地逼近所要求的阶跃信号&#xff0c;可使对象运行平稳&#xff0c;适用于高精度伺服系统的位置跟踪。在步进式PID控…

【数据手册】CH340G芯片使用介绍

1.概述 CH340是一系列USB总线适配器&#xff0c;它通过USB总线提供串行、并行或IrDA接口。CH340G集成电路提供通用的MODEM信号&#xff0c;允许将UART添加到计算机上&#xff0c;或将现有的UART设备转换为USB接口。 2.特征 全速USB接口&#xff0c;兼容USB 2.0接口。使用最小…

Android核心技术【SystemServer加载AMS】

启动流程 Init 初始化Linux 层&#xff0c;处理部分服务 挂载和创建系统文件 解析rc文件&#xff1a; rc 文件中有很多action 进入无限循环 执行action&#xff1a;zygote 进程就在这里启动 for循环去解析参数&#xff0c;根据rc 文件中的action 执行相应操作 检测并重启需要…

细谈文件操作

该文章将详细的介绍文件操作这方面的知识&#xff0c;文件的打开&#xff0c;关闭&#xff0c;读取&#xff0c;写入&#xff0c;以及相关的函数都会在本文一一介绍&#xff0c;干货满满喔&#xff01;1.为什么使用文件2.什么是文件2.1程序文件2.2数据文件2.3文件名3.文件的打开…

SpringBoot(java)操作elasticsearch

elasticsearch我已经装了ik&#xff0c;中文分词器。已经使用容器搭建了集群。之前在我的博客-elasticsearch入门中&#xff0c;已经介绍了http请求操纵es的基本功能&#xff0c;java API功能和他一样&#xff0c;只是从http请求换成了javaApi操作。springBoot里继承了elastics…

蓝桥杯算法训练合集八 1.数的划分2.求先序排列3.平方计算4.三角形高5.单词复数

目录 1.数的划分 2.求先序排列 3.平方计算 4.三角形高 5.单词复数 1.数的划分 问题描述 将整数n分成k份&#xff0c;且每份不能为空&#xff0c;任意两份不能相同(不考虑顺序)。 例如&#xff1a;n7&#xff0c;k3&#xff0c;下面三种分法被认为是相同的。 1&#xff0c…

关于宏文档开启宏后还是不能正常使用问题

1.问题 2.开启宏 (62条消息) [Win10Excel365]尽管已启用VBA宏&#xff0c;Excel还是无法运行宏_逍遥猴哥的博客-CSDN博客 3. 问题还是没解决 发现可能是字体显示乱码&#xff0c;导致vba运行找不到争取路径 VBA编辑器中中文乱码的解决办法&#xff1a;1、依次点击【工具→选项…

如何写一个命令行解释器(SHELL)

文章目录前言什么是命令行解释器 ——SHELLSHELL的结构void print_info(char ** env) //打印命令行信息函数void read_comand(char **buffer) //读取指令函数char **split_line(char *buffer, int *flag) //分割字符串函数int excute_line(char **buffer, int flag) // 执行指令…

Redis 安全汇总小结

Redis redis 是一个C语言编写的 key-value 存储系统&#xff0c;可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。它通常被称为数据结构服务器&#xff0c;因为值&#xff08;value&#xff09;可以是 字符串(String), 哈希(Hash), 列表(list…

电子技术——基本MOS放大器配置

电子技术——基本MOS放大器配置 上一节我们探究了一种MOS管的放大器实现&#xff0c;其实MOS放大器还有许多变种配置&#xff0c;在本节我们学习最基本的三大MOS放大器配置&#xff0c;分别是共栅极&#xff08;CG&#xff09;、共漏极&#xff08;CD&#xff09;、共源极&…