浅谈关于LCA
prologue
本身只会 tarjan 和 倍增法求LCA 的,但在发现有一种神奇的\(O(1)\) 查询 lca 的方法,时间优化很明显。
main body
倍增法
先讨论倍增法,倍增法求 lca 是一种很常见普遍的方法,这里直接放代码了,其本身的内核就是让较低点每次都跳 $ 2 ^ k $ 步,如果跳的比另一个高了,就不跳那么高,跳 \(2 ^ {k-1}\) 步,这就用对数级的复杂度求出来了 LCA。
code
比较基础,建议直接背过。
inline void bfs()
{
memset(dep, 0x3f, sizeof dep);
ll hh = 0, tt = -1;
q[++ tt] = 1;
dep[0] = 0, dep[1] = 1;
while(hh <= tt)
{
ll u = q[hh ++ ];
for(rl i = h[u]; ~i; i = ne[i])
{
ll v = e[i];
if(dep[v] > dep[u] + 1)
{
dep[v] = dep[u] + 1;
fa[v][0] = u;
q[++ tt] = v;
for(rl k=1; k <= 20; ++ k)
fa[v][k] = fa[fa[v][k - 1]][k - 1];
}
}
}
}
inline ll lca(ll a, ll b)
{
if(dep[a] < dep[b]) swap(a, b);
for(rl k=20; k >= 0; -- k)
if(dep[fa[a][k]] >= dep[b])
a = fa[a][k];
if(a == b) return a;
for(rl k=20; k >= 0; -- k)
if(fa[a][k] != fa[b][k])
a = fa[a][k], b = fa[b][k];
return fa[a][0];
}
tarjan求LCA
这是一种离线做法(将所有的询问存下来,然后再一一输出)。
这种做法我自我感觉不太好用,但是因为这种做法的时间复杂度是 \(O(n + m)\) 的,所以说有的人用起来很香(个人不喜欢,主要是没怎么敲过/
#include <bits/stdc++.h>
using namespace std;
#define ll int
#define rl register ll
const ll N = 20010, M = 2 * N;
ll n, m;
ll tot, ne[M], e[M], h[N], w[M];
ll p[N], dis[N], st[N];
ll res[N];
vector<pair<int, int>> query[N];
inline void add(ll a, ll b, ll c)
{
ne[++tot] = h[a], h[a] = tot, e[tot] = b, w[tot] = c;
}
inline void dfs(ll u, ll fa)
{
for(rl i=h[u]; ~i; i = ne[i])
{
ll v = e[i];
if(v == fa) continue;
dis[v] = dis[u] + w[i];
dfs(v, u);
}
}
inline ll find(ll x)
{
if(p[x] == x) return x;
else return p[x] = find(p[x]);
}
inline void tarjan(ll u)
{
st[u] = 1;
for(rl i=h[u]; ~i; i = ne[i])
{
ll v = e[i];
if(!st[v])
{
tarjan(v);
p[v] = u;
}
}
for(auto item : query[u])
{
ll y = item.first, id = item.second;
if(st[y] == 2)
{
ll anc = find(y);
res[id] = dis[y] + dis[u] - 2 * dis[anc];
}
}
st[u] = 2;
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof h);
for(rl i=1; i <= n - 1; ++ i)
{
ll a, b, c;
cin >> a >> b >> c;
add(a, b, c), add(b, a, c);
}
for(rl i=1; i <= m; ++ i)
{
ll a, b;
cin >> a >> b;
if(a != b)
{
query[a].push_back({b, i});
query[b].push_back({a, i});
}
}
for(rl i=1; i <= n; ++ i) p[i] = i;
dfs(1, -1);
tarjan(1);
for(rl i=1; i <= m; ++ i)
cout << res[i] << endl;
return 0;
}
O(1) 复杂度求LCA
注意,如果你会对这个图删删减减,加加变变,那么这个变化之后就不能直接查询了(st表带来的)。
我们首先求出来树上每个点的 \(dfs\) 序列。
我们考虑 \(u\) 和 \(v\) 之间有什么,令 \(lca(u, v) = x, dfn_u < dfn_v\)。那么 \(x\) 到 \(v\) 路径上的一点,一定是 \(u \to v\) 路径上一点,并且这个点是 \(u \to v\) 深度最小的,这个点的父亲节点就是 \(x\)。
再考虑一种特殊情况,当\(u\) 就是 \(v\) 的祖先的时候,深度最小得点就变成了\(u\), 为了规避这种情况,我们就选择在 \([dfn_u + 1 \to dfn_v]\) 上来查询。
区间深度最小可以使用 ST表 来维护。
下面是代码,也建议背过。
inline ll get(ll a, ll b) { return dep[a] < dep[b] ? a : b; }
inline void dfs(ll u, ll fa)
{
dfn[u] = ++ idx, st[0][idx] = fa, dep[u] = dep[fa] + 1;
for(rl i=h[u]; ~i; i = ne[i])
{
ll v = e[i];
if(v == fa) continue;
dfs(v, u);
}
}
inline void init()
{
dfs(1, -1);
for(rl i=1; (1 << i) <= n; ++ i)
for(rl j=1; j <= n - (1 << i) + 1; ++ j)
st[i][j] = get(st[i-1][j], st[i-1][j + (1 << i - 1)]);
}
inline ll lca(ll a, ll b)
{
if(a == b) return a;
a = dfn[a], b = dfn[b];
if(a > b) swap(a, b);
ll l = __lg(b - a); // 本人亲测用__lg(b - a),和log2(b - a) 都可以,这两个得区别可以上网搜。
return get(st[l][a + 1], st[l][b - (1 << l) + 1]);
}
浅谈关于LCA的更多相关文章
- 浅谈求lca
lca即最近公共祖先,求最近公共祖先的方法大概有3种,其实是窝只听说过3种,这3种做法分别是倍增求lca,树剖求lca和tarjan求lca,但是窝只会前2种,所以这里只说前2种算法了. 首先是倍增求 ...
- 浅谈倍增LCA
题目链接:https://www.luogu.org/problemnew/show/P3379 刚学了LCA,写篇blog加强理解. LCA(Least Common Ancestors),即最近公 ...
- 莫队浅谈&题目讲解
莫队浅谈&题目讲解 一.莫队的思想以及莫队的前置知识 莫队是一种离线的算法,他的实现借用了分块的思想.在学习莫队之前,本人建议学习一下分块,并对其有一定的理解. 二.莫队 现给出一道例题:bz ...
- 浅谈 Fragment 生命周期
版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Fragment 文中如有纰漏,欢迎大家留言指出. Fragment 是在 Android 3.0 中 ...
- 浅谈 LayoutInflater
浅谈 LayoutInflater 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/View 文中如有纰漏,欢迎大家留言指出. 在 Android 的 ...
- 浅谈Java的throw与throws
转载:http://blog.csdn.net/luoweifu/article/details/10721543 我进行了一些加工,不是本人原创但比原博主要更完善~ 浅谈Java异常 以前虽然知道一 ...
- 浅谈SQL注入风险 - 一个Login拿下Server
前两天,带着学生们学习了简单的ASP.NET MVC,通过ADO.NET方式连接数据库,实现增删改查. 可能有一部分学生提前预习过,在我写登录SQL的时候,他们鄙视我说:“老师你这SQL有注入,随便都 ...
- 浅谈WebService的版本兼容性设计
在现在大型的项目或者软件开发中,一般都会有很多种终端, PC端比如Winform.WebForm,移动端,比如各种Native客户端(iOS, Android, WP),Html5等,我们要满足以上所 ...
- 浅谈angular2+ionic2
浅谈angular2+ionic2 前言: 不要用angular的语法去写angular2,有人说二者就像Java和JavaScript的区别. 1. 项目所用:angular2+ionic2 ...
- iOS开发之浅谈MVVM的架构设计与团队协作
今天写这篇博客是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇博客的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦 ...
随机推荐
- ElasticSearch的使用和介绍
1.概述 功能 Elasticsearch 是一个分布式的 RESTful 搜索和分析引擎,可用来集中存储您的数据,以便您对形形色色.规模不一的数据进行搜索.索引和分析. 例如: 在电商网站搜索商品 ...
- pcie reset系列之 内核框架
FLR是pci reset的一种. 关于FLR的寄存器操作比较简单, 相关的寄存器有: 配置空间里device cap里的FLR capability bit, 这个表示设备是否支持FLR. 配置空间 ...
- EnhancingDecisionTreeswithGeographicInformationSystemsa
目录 引言 在计算机科学领域,地理信息系统和( geographical information systems, GIS)已经成为了一个非常受欢迎的工具.GIS 可以用来处理和存储大量的地理数据,支 ...
- 将 -Xms 参数设置和-Xmx 参数的相等,对比 -Xms参数 设置为-Xmx 参数的一半,有哪些优势?
将 -Xms 参数设置为与 -Xmx 参数相等,相比于将 -Xms 参数设置为 -Xmx 参数的一半,具有以下优势: 1. 程序启动时间更短 当将 -Xms 参数设置为与 -Xmx 参数相等时,JVM ...
- 获得 markdown 无序列表格式的文件目录树
tree 命令可以获得文件目录结构,但是放在文档中时,我想用 markdown 无序列表的形式,在编辑器内还可以折叠. 完整解决方案:在 ~/.oh-my-zsh/custom 下添加下述自定义函数( ...
- .NET周刊【7月第2期 2023-07-09】
由于这周比较忙,只给出了标题和链接,没有具体的简介. 另外根据粉丝朋友的反馈,".NET周报" 更名为 ".NET周刊",希望大家喜欢 : ) 国内文章 Ava ...
- 详解TCP网络协议栈的工作原理
本文分享自华为云社区<网络通信的神奇之旅:解密Linux TCP网络协议栈的工作原理>,作者: Lion Long . 一.TCP网络开发API TCP,全称传输控制协议(Transmis ...
- spring cloud微服务搭建配置中心之携程开源框架Apollo
1.Apollo(阿波罗) Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境.不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限.流程治理等特性,适 ...
- 从0开发属于自己的nestjs框架的mini 版 —— ioc篇
如今,nodejs的框架也是层出不穷,偏向向底层的有 express.koa. Fastify,偏向于上层有阿里的 Egg.thinkjs .还有国外的 nestjs. 在这里我更喜欢 nestjs, ...
- Flutter系列文章-Flutter进阶2
这一节我将再详细地为您介绍 Flutter 进阶主题,包括导航和路由.状态管理.异步处理.HTTP请求和Rest API,以及数据持久化.让我们逐个介绍这些主题. 1.导航和路由 在 Flutter ...