首页 > 编程语言 >【算法学习笔记】DFN序求LCA(最近公共祖先)

【算法学习笔记】DFN序求LCA(最近公共祖先)

时间:2023-08-17 20:45:36浏览次数:40  
标签:int edge DFN 序求 st 祖先 dfn LCA

前置知识

  • DFN序:对一棵树进行深度优先搜索DFS得到的结点序列,即深度优先搜索DFS的访问顺序。该表述不一定严谨,建议百度
  • ST表(Sparse Table,稀疏表)

算法概述

引理 1.1

在 DFN序 中祖先一定出现后代之前。

考虑一树上的两个节点 \(x\),\(y\) 的最近公共祖先 \(d\) ,设 \(x\) 的 DFN序 小于等于 \(y\) 的 DFN序。

当 \(x\) 不为 \(y\) 的祖先时

引理1.1 可得,\(d\) 及其祖先的 DFN 序不在 DFN 序的 \([x,y]\) 区间内。
设 \(d\) 一儿子 \(d'\),满足包含该儿子且以 \(d\) 为根的子树 包含 \(v\)。
显然,\(d'\) 的 DFN 序属于 DFN 序的 \([x,y]\) 区间,即在 DFN 序的 \([x,y]\) 区间内寻找在该区间 DFN 序最小的节点,那个节点的父亲就是 \(d\)。

当 \(x\) 为 \(y\) 的祖先时

那么只需由 在 DFN 序的 \([x,y]\) 区间内寻找 变为 在 DFN 序的 \([x - 1,y]\) 区间内寻找。
因为 当 \(x\) 不为 \(y\) 的祖先时 \(x\neq y\)
所以 算法微调后正确性仍能保证。

需要注意的是,当 \(x=y\) 时,需要特判。

综上所述,当 \(x\neq y\) 时,在 DFN 序的 \([x - 1,y]\) 区间内寻找 DFN 序最小的节点;
当 \(x=y\) 时,特判。

实现

可用 ST表 实现查询。预处理时间复杂度 \(O(n\log n)\),查询时间复杂度 \(O(1)\)。

板子-> Luogu P3379 【模板】最近公共祖先(LCA)

#include <iostream>
#include <cmath>
#include <vector>

using namespace std;

const int log2n = 19;

vector<int> edge[500010];
int dfn[500010];
int st[log2n + 1][500010];
int cur;
#define min(x, y) (dfn[x] < dfn[y] ? x : y)

void dfs(int u, int fa)
{
	dfn[u] = ++cur;
	st[0][dfn[u]] = fa;
	for (auto v : edge[u])
		if (v != fa)
			dfs(v, u);
	return;
}

int lca(int x, int y)
{
	if (x == y)
		return x;
	x = dfn[x];
	y = dfn[y];
	if (y < x)
		swap(x, y);
	int log2xy = log2(y - x++); /* 此处可用预处理实现O(1) */
	return min(st[log2xy][x], st[log2xy][y - (1 << log2xy) + 1]);
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int n, m, root;
	
	cin >> n >> m >> root;
	
	for (int i = 1, x, y; i < n; ++i)
		cin >> x >> y, edge[x].push_back(y), edge[y].push_back(x);
	dfs(root, -1);
	for (int i = 1; i <= log2n; ++i)
		for (int j = 1; j + (1 << i) - 1 <= n; ++j)
			st[i][j] = min(st[i - 1][j], st[i - 1][j + (1 << i - 1)]);
	while (m--)
	{
		int u, v;
		
		cin >> u >> v;
		cout << lca(u, v) << "\n";
	}
	return 0;
}

码风丑陋,敬请谅解

标签:int,edge,DFN,序求,st,祖先,dfn,LCA
From: https://www.cnblogs.com/Miyamizu-Mitsuha/p/17638807.html

相关文章

  • P3629 巡逻 LCA题解
    原题:洛谷P3629问题转化首先,给定的图是一个有\(n\)个点,\(n-1\)条边的无向连通图,这个图就等价于一棵树。不建立新的道路时,从\(1\)号节点出发,把整棵树上的每条边遍历至少一次,再回到\(1\)号节点,会恰好经过每条边两次,路线总长度为\(2(n-1)\),如下图最左边的部分所示。根据树......
  • [YsOI2023] 广度优先遍历 逆向输出路径(分层建树拓扑序. LCA)
    今天的模板测试是无向图上的广度优先遍历,【数据删除】马上写好了代码:1#include<cstdio>2#include<cstring>3#include<iostream>4#include<algorithm>5#include<vector>6#include<queue>7usingnamespacestd;8constintmaxn=100005;......
  • CorelCAD中文版下载-CorelCAD 2021(CAD设计工具) 官方版特色
    CorelCAD是一款CAD软件,可以帮助用户设计和绘制2D和3D图形。它提供了许多功能和工具,包括绘图、编辑、注释、测量和布局等。CorelCAD支持多种文件格式,包括DWG、DXF、DWF和PDF等,可以与其他CAD软件进行互操作。此外,CorelCAD还提供了一些高级功能,例如3D建模、渲染、动画和脚本等,可帮助用......
  • 【图论】最近公共祖先(LCA)
    什么是LCA最近公共祖先是相对于两个节点来说的,顾名思义,最近公共祖先即为两个节点公共的最近的祖先。如上图,\(86\)和\(67\)的\(LCA\)是\(75\),\(67\)和\(27\)的\(LCA\)是\(50\)。怎么求LCA倍增法我们先想想暴力怎么做:从这两个节点开始,一步一步往上跳,直到跳到了一......
  • 关于 LCA 的简单记录
    最近做了几道LCA的题目。所以总结一下。首先我们来回顾一下倍增求LCA的流程吧。首先是初始化:进行bfs。处理出每层的深度。处理每个节点的\(2^k\)级父亲,方式为一个递推,即为由\(2^{k-1}\)级祖先的\(2^{k-1}\)祖先推出\(2^k\)级祖先。然后是每次的查询:把y......
  • 最近公共祖先(LCA)
    「观前提醒」「文章仅供学习和参考,如有问题请在评论区提出」目录前言定义性质求LCA倍增算法Trajan算法树链剖分基本概念基本性质具体实现参考资料前言简单的模板整理,只是概括了一下具体的实现方法(说到底是给自己写的),如果看不明白可以去看原视频(讲的很好),链接在参考资料里......
  • 进程注入检测 —— RtlCaptureStackBackTrace 获取当前函数的调用栈函数
    https://stackoverflow.com/questions/590160/how-to-log-stack-frames-with-windows-x64 https://cpp.hotexamples.com/examples/-/-/RtlCaptureStackBackTrace/cpp-rtlcapturestackbacktrace-function-examples.html  例子参考  平日里用VS开发工具在调时在Debug下有一个选......
  • LCA
    目录LCA最近公共祖先例题模板题综合题相关资料LCA最近公共祖先最近公共祖先简称LCA(LowestCommonAncestor)。两个节点的最近公共祖先,就是这两个点的公共祖先里面,离根最远的那个。暴力求解太慢,这里先摘记一种做法-倍增法。对于每一个节点,我们先通过dfs预先处理出当前节点向根......
  • PascalCase & camelCase & kebabCase介绍
    原文链接:https://www.cnblogs.com/qdkfyym/p/13528076.html一、PascalCase  帕斯卡拼写法(也叫大骆驼拼写法)  PascalCase 帕斯卡拼写法是一种计算机编程中的变量命名方法。它主要的特点是将描述变量作用所有单词的首字母大写,然后直接连接起来,单词之间没有连接符。比......
  • 2167 - 树的公共祖先(LCA)
    题目描述给定一棵树和两个不同的结点,求出他们最近的公共祖先父结点。已知该树有n个结点,标号1..n。输入第1行输入一个整数nn,代表结点数量(n≤100)第2行输入两个整数x,yx,y,表示需要计算的结点;以下n−1行,每行两个整数a和b,表示a的父结点是b。输出输......