lca 学习笔记
定义
- 最近公共祖先简称 \(LCA\)
- 两个节点的最近公共祖先,就是这两个点的公共祖先里,离根最远的的那个
- 为了方便,我们记某点集 \(S={v1,v2,...,vn}\) 的最近公共祖先为 \(LCA(v1,v2,...,vn)\) 或 \(LCA(S)\)
LCA的有用的性质
- \(1.\) \(lca(x)=x\)
- \(2.\) 每两个点的 \(lca\) 唯一
- \(3.\) 如果 \(x\) 与 \(y\) 互相不是对方的祖先,则两点位于 \(lca(x,y)\) 的两颗不同子树中
- \(4.\) \(重要\) 两点的 \(lca\) 定在两点的最短路上 (那么 \(x\) 到 \(y\) 的最短路即为 \(x->...->lca(x,y)->...->y\))
- \(5.\) 由第4条可推出,\(d(x,y)=h(x)+h(y)-2h(lca(x,y))\) (d是两点距离,h为点到根的距离)
实现算法
- 本人只会树剖算法 \(qwq\)
- 先来两遍 \(dfs\) ,然后 \(lca\) ,属于在线算法,时间复杂度为 \(O(2*n+n*log(n))\)
- 下面代码求的是两点的 \(lca\)
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=5e5+1;
int n,m,s;
int fa[N],son[N],dep[N],top[N],sz[N];
vector<int>e[N];
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=1;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
void dfs1(int x,int father)
{
fa[x]=father,dep[x]=dep[father]+1,sz[x]=1;
for(int y:e[x])
if(y!=father)
{
dfs1(y,x);
sz[x]+=sz[y];
if(sz[son[x]]<sz[y]) son[x]=y;
}
}
void dfs2(int x,int t)
{
top[x]=t;
if(!son[x]) return ;
dfs2(son[x],t);
for(int y:e[x])
if(y!=fa[x]&&y!=son[x])
dfs2(y,y);
}
int lca(int x,int y)
{
for(;top[x]!=top[y];x=fa[top[x]])
if(dep[top[x]]<dep[top[y]]) swap(x,y);
return dep[x]<dep[y]?x:y;
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
int x,y;
read(n),read(m),read(s);
for(int i=1;i<n;i++)
read(x),read(y),
e[x].push_back(y),
e[y].push_back(x);
dfs1(s,0),dfs2(s,s);
while(m--)
read(x),read(y),
cout<<lca(x,y)<<endl;
}
- 样例输入
12 5 1
1 2
1 3
1 4
2 5
2 6
3 7
4 8
4 9
6 10
8 11
8 12
5 6
10 5
5 9
7 9
11 12 - 样例输出
2
2
1
1
8
如何A题
- \(1.\) 常与 \(线段树\) \(树状数组\) \(最短路\) 等算法同用。
- \(2.\) \(lca\) 只能在树中使用,否则会 \(RE\) 所以有环图中需要 \(kru\) 重构树
- \(3.\) 有时用到边权但不好解决时,可以在 \(x\)、\(y\)两点中插入一个点,使其点权为原边权,最后输出点权即可
- 在 \(货车运输\) 中运用了前两条,详见 货车运输 题解链接
- \(4.\) 最短路时,两点间最短距离用到前面的 \(性质5\),例题 Distance Queries 距离咨询
- \(5.\) 由于我们没有学习倍增的 \(lca\) ,所以有时超时时要用到树状数组的 \(差分\) ,例题DFS 序 3,树上差分 1
,通常修改两点最短路径上所有权值时要用到 - \(Distance Queries 距离咨询\) 代码
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define f t[p]
#define ls p<<1
#define rs p<<1|1
using namespace std;
const int N=4e5+1;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=1;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,m,k,cnt;
int a[N],fa[N],son[N],dep[N],top[N],sz[N],d[N];
int head[N],to[N],nxt[N],w[N],tot;
void add(int x,int y,int z)
{
nxt[++tot]=head[x];
to[tot]=y;
w[tot]=z;
head[x]=tot;
}
void dfs1(int x,int t)
{
fa[x]=t,dep[x]=dep[t]+1,sz[x]=1;
for(int i=head[x],y;i;i=nxt[i])
if((y=to[i])!=t)
{
dfs1(y,x);
sz[x]+=sz[y];
if(sz[son[x]]<sz[y]) son[x]=y;
}
}
void dfs2(int x,int t)
{
top[x]=t;
if(!son[x]) return ;
dfs2(son[x],t);
for(int i=head[x],y;i;i=nxt[i])
if((y=to[i])!=fa[x]&&y!=son[x])
dfs2(y,y);
}
void build(int x)
{
for(int i=head[x],y;i;i=nxt[i])
if((y=to[i])!=fa[x])
d[y]=d[x]+w[i],
build(y);
}
int lca(int x,int y)
{
for(;top[x]!=top[y];x=fa[top[x]])
if(dep[top[x]]<dep[top[y]]) swap(x,y);
return dep[x]<dep[y]?x:y;
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
int x,y,z;char s;
read(n),read(m);
while(m--)
read(x),read(y),read(z),cin>>s,
add(x,y,z),add(y,x,z);
dfs1(1,0),dfs2(1,1),build(1);
read(k);
while(k--)
read(x),read(y),
cout<<(d[x]+d[y]-d[lca(x,y)]*2)<<endl;//x到根节点的距离+y到根节点的距离-2*公共祖先到根节点的距离
}
- \(DFS 序 3,树上差分 1\) 代码
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e6+10;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=1;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,m,root,cnt;
int a[N],fa[N],son[N],rk[N],sz[N],in[N],out[N],dep[N],top[N],c1[N],c2[N];
vector<int>e[N];
void dfs1(int x,int t)
{
fa[x]=t,dep[x]=dep[t]+1,sz[x]=1,in[x]=++cnt,rk[cnt]=x;
for(int y:e[x])
if(y!=t)
{
dfs1(y,x);
sz[x]+=sz[y];
if(sz[son[x]]<sz[y]) son[x]=y;
}
out[x]=cnt;
}
void dfs2(int x,int t)
{
top[x]=t;
if(!son[x]) return ;
dfs2(son[x],t);
for(int y:e[x])
if(y!=fa[x]&&y!=son[x])
dfs2(y,y);
}
int lca(int x,int y)
{
for(;top[x]!=top[y];x=fa[top[x]])
if(dep[top[x]]<dep[top[y]]) swap(x,y);
return dep[x]<dep[y]?x:y;
}
int lowbit(int x){return x&(~x+1);}
void change(int x,int y)
{
if(x)
for(int i=x;i<=n;i+=lowbit(i))
c1[i]+=y,
c2[i]+=(dep[rk[x]]*y);
}
void change(int x,int y,int z)
{
int f=lca(x,y);
change(in[x],z),change(in[y],z),
change(in[f],~z+1),change(in[fa[f]],~z+1);
}
int ask1(int x)
{
int ans=0;
while(x) ans+=c1[x],x-=lowbit(x);
return ans;
}
int ask2(int x)
{
int ans=0;
while(x) ans+=c2[x],x-=lowbit(x);
return ans;
}
int ask_it(int x) {return (ask1(out[x])-ask1(in[x]-1));}
int ask_tree(int x) {return ask2(out[x])-ask2(in[x]-1)-(dep[x]-1)*ask_it(x);}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
int x,y,z,s;
read(n),read(m),read(root);
for(int i=1;i<=n;i++) read(a[i]);
for(int i=1;i<n;i++)
read(x),read(y),
e[x].push_back(y),
e[y].push_back(x);
dfs1(root,0),dfs2(root,root);
for(int i=1;i<=n;i++) change(i,i,a[i]);
while(m--)
{
read(s);
if(s==1) read(x),read(y),read(z),change(x,y,z);
else if(s==2) read(x),cout<<ask_it(x)<<endl;
else read(x),cout<<ask_tree(x)<<endl;
}
}
lca 学习笔记的更多相关文章
- LCA学习笔记
写在前面 目录 一.LCA的定义 二.暴力法求LCA 三.倍增法求LCA 四.树链剖分求LCA 五.LCA典型例题 题目完成度 一.LCA的定义 LCA指的是最近公共祖先.具体地,给定一棵有根树,若结 ...
- 倍增LCA学习笔记
前言 "倍增",作为一种二进制拆分思想,广泛用于各中算法,如\(ST\)表,求解\(LCA\)等等...今天,我们仅讨论用该思想来求解树上两个节点的\(LCA\)(最近公共祖先 ...
- 倍增求LCA学习笔记(洛谷 P3379 【模板】最近公共祖先(LCA))
倍增求\(LCA\) 倍增基础 从字面意思理解,倍增就是"成倍增长". 一般地,此处的增长并非线性地翻倍,而是在预处理时处理长度为\(2^n(n\in \mathbb{N}^+)\ ...
- 树链剖分 树剖求lca 学习笔记
树链剖分 顾名思义,就是把一课时分成若干条链,使得它可以用数据结构(例如线段树)来维护 一些定义: 重儿子:子树最大的儿子 轻儿子:除了重儿子以外的儿子 重边:父节点与重儿子组成的边 轻边:除重边以外 ...
- LCA学习笔记(原洛谷文章)
本文原发布时间:\(\texttt{2022-05-21 14:11:52}\) 简介 最经公共祖先 \(\operatorname{LCA}(a,b)=c\),指的是在一棵树上节点 \(a\) 与 ...
- 算法学习笔记(5): 最近公共祖先(LCA)
最近公共祖先(LCA) 目录 最近公共祖先(LCA) 定义 求法 方法一:树上倍增 朴素算法 复杂度分析 方法二:dfs序与ST表 初始化与查询 复杂度分析 方法三:树链剖分 DFS序 性质 重链 重 ...
- kruskal重构树学习笔记
\(kruskal\) 重构树学习笔记 前言 \(8102IONCC\) 中考到了,本蒟蒻不会,所以学一下. 前置知识 \(kruskal\) 求最小(大)生成树,树上求 \(lca\). 算法详 ...
- 仙人掌&圆方树学习笔记
仙人掌&圆方树学习笔记 1.仙人掌 圆方树用来干啥? --处理仙人掌的问题. 仙人掌是啥? (图片来自于\(BZOJ1023\)) --也就是任意一条边只会出现在一个环里面. 当然,如果你的图 ...
- Day 4 学习笔记 各种图论
Day 4 学习笔记 各种图论 图是什么???? 不是我上传的图床上的那些垃圾解释... 一.图: 1.定义 由顶点和边组成的集合叫做图. 2.分类: 边如果是有向边,就是有向图:否则,就是无向图. ...
- 树上启发式合并(dsu on tree)学习笔记
有丶难,学到自闭 参考的文章: zcysky:[学习笔记]dsu on tree Arpa:[Tutorial] Sack (dsu on tree) 先康一康模板题吧:CF 600E($Lomsat ...
随机推荐
- 京东搜索EE链路演进
导读 搜索系统中容易存在头部效应,中长尾的优质商品较难获得充分的展示机会,如何破除系统的马太效应,提升展示结果的丰富性与多样性,助力中长尾商品成长是电商平台搜索系统的一个重要课题.其中,搜索EE系统在 ...
- 文心一言 VS 讯飞星火 VS chatgpt (91)-- 算法导论8.3 4题
四.用go语言,说明如何在 O(n) 时间内,对 0 到 $n^3-1$ 区间内的n个整数进行排序. 文心一言: 在 O(n) 时间内对 0 到 n^3-1 区间内的 n 个整数进行排序,可以使用基数 ...
- 兴达易控Modbus转Profinet 网关连接 ACS510 变频器配置案例
案例简介: 该案例为兴达易控Modbus转Profinet网关(XD-MDPN100)将ABB ACS510 变频器接入西门子 1200PLC.需要设备为西门子 PLC1200.ACS510 变频器. ...
- 文心一言 VS 讯飞星火 VS chatgpt (105)-- 算法导论10.1 3题
三.用go语言,仿照图 10-2,画图表示依次执行操作 ENQUEUE(Q,4).ENQUEUE(Q,1).ENQUEUE(Q,3).DEQUEUE(Q).ENQUEUE(Q,8)和 DEQUEUE( ...
- Python中的可迭代对象和迭代器
1.可迭代对象 1.1.可迭代对象概念 可迭代对象,最直观的感觉就是可以使用for来循环迭代每一个元素.例如Python内置的类型:str.list.tuple.dict等类型的对象,都是可迭代对象. ...
- Django框架项目——BBS项目介绍、表设计、表创建同步、注册、登录功能、登录功能、首页搭建、admin、头像、图片防盗、个人站点、侧边栏筛选、文章的详情页、点赞点踩、评论、后台管理、添加文章、头像
文章目录 1 BBS项目介绍.表设计 项目开发流程 表设计 2 表创建同步.注册.登录功能 数据库表创建及同步 注册功能 登陆功能 3 登录功能.首页搭建.admin.头像.图片防盗.个人站点.侧边栏 ...
- SpringBoot2.7升级到3.0的实践分享
背景 最近把项目中的技术框架做一次升级,最重要的就是SpringBoot从2.7.x升级到3.0.x,当然还会有一些周边的框架也会连带着升级,比如Mybatis Plus,SpringCloud等,话 ...
- Java 魔法值处理的四种方法
Java 魔法值处理方案 魔法值的定义 方法一 静态常量(不推荐) 方法二 接口中定义 方法三 定义在实体类 方法四 使用枚举类 enum 总结 魔法值的定义 魔法值是Java中突兀出现在代码中的常量 ...
- 2020/4/26 2-sat 学习笔记
2-sat 吧.... 其实我jio得它一点都不难 嗯 2-sat是个啥东西呢?其实就是有很多人,他们每个人有两个要求,一个要求可以说是要求一个数为0或1而对于第i个数,我们可以选择为0或为1最终询问 ...
- 用Rust手把手编写一个Proxy(代理), 准备篇, 动手造轮子
用Rust手把手编写一个Proxy(代理), 准备篇, 动手造轮子 wmproxy 将实现http/https代理, socks5代理, 后续将实现websocket代理, 内外网穿透等, 会将实现过 ...