浅谈倍增法求解LCA
Luogu P3379 最近公共祖先
原题展现
题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入格式
第一行包含三个正整数 \(N,M,S\),分别表示树的结点个数、询问的个数和树根结点的序号。
接下来 \(N-1\) 行每行包含两个正整数 \(x, y\),表示 \(x\) 结点和 \(y\) 结点之间有一条直接连接的边(数据保证可以构成树)。
接下来 \(M\) 行每行包含两个正整数 \(a, b\),表示询问 \(a\) 结点和 \(b\) 结点的最近公共祖先。
输出格式
输出包含 \(M\) 行,每行包含一个正整数,依次为每一个询问的结果。
样例输入 #1
5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
样例输出 #1
4
4
1
4
4
提示
对于 \(30\%\) 的数据,\(N\leq 10\),\(M\leq 10\)。
对于 \(70\%\) 的数据,\(N\leq 10000\),\(M\leq 10000\)。
对于 \(100\%\) 的数据,\(N\leq 500000\),\(M\leq 500000\)。
样例说明:
该树结构如下:
第一次询问:\(2, 4\) 的最近公共祖先,故为 \(4\)。
第二次询问:\(3, 2\) 的最近公共祖先,故为 \(4\)。
第三次询问:\(3, 5\) 的最近公共祖先,故为 \(1\)。
第四次询问:\(1, 2\) 的最近公共祖先,故为 \(4\)。
第五次询问:\(4, 5\) 的最近公共祖先,故为 \(4\)。
故输出依次为 \(4, 4, 1, 4, 4\)。
解析
本题是 LCA 的模板
LCA 的做法很多,比如暴力跳,倍增
暴力跳
让深度大的一点不断向上跳,直到两点深度相等
如果两点深度相同但是并不相等,可以两点一起跳
在随机数据下表现优异,因为树会比较平衡,所以近似\(O(\log n)\)
通常会被卡成单次\(O(n)\),其实不难构造,可以构造一个深度大的树(比如链)
本人出的一道题思想类似这样,不过这道题保证了平衡
倍增法
考虑一次跳多一点
记\(fa_{u,k}\)表示距离\(u\)的边数为\(2^k\)的祖先节点则\(fa_{u,k}=fa_{fa_{u,k-1},k-1}\)可以通过dfs求出\(fa\)
如果求LCA,我们可以很快让两点来到相同的深度
考虑求两点深度差,将差二进制拆分,每次跳一个\(2\)的幂,时间复杂度\(O(\log n)\)
当然,没必要真的二进制拆分,因为我们要知道是\(2\)的几次幂,所以用cmath
的log2
更加方便
这里有一个优化:用\(O(n)\)的时间复杂度递推求出log2
的值
然后,如果两点深度相同不相等,有一个自认为巧妙的方法求解
一个性质:如果两点跳到LCA了,继续向上跳依然相等(易证)
如果两点向上跳不相等,那么一定可以继续跳
于是想到一个办法:尝试枚举\(i\)从\(31\)到\(0\),表示尝试跳\(2^i\)
如果向上跳不相同的话,就向上跳,这样,枚举完,LCA就是\(fa_{x,0}\)
核心代码如下,首先是预处理
void dfs(long long x,long long fa)
{
f[x][0]=fa;
dep[x]=dep[fa]+1;
for(int i=1;i<=31;i++)
{
f[x][i]=f[f[x][i-1]][i-1];
}
for(int i=h[x];i;i=a[i].next)
{
if(a[i].to!=fa)
{
dfs(a[i].to,x);
}
}
}
然后是求解
if(dep[x]<dep[y])
{
swap(x,y);
}
while(dep[x] > dep[y])
{
x = f[x][lg[dep[x]-dep[y]] - 1];
}
if(x==y)
{
cout<<x<<endl;
continue;
}
for(int k = lg[dep[x]] - 1; k >= 0;k--)
{
if(f[x][k] != f[y][k])
{
x = f[x][k], y = f[y][k];
}
}
于是,我们得到了一个严格的\(O(\log n)\)算法
Luogu P1967 [NOIP2013 提高组] 货车运输
原题展现
题目描述
A 国有 \(n\) 座城市,编号从 \(1\) 到 \(n\),城市之间有 \(m\) 条双向道路。每一条道路对车辆都有重量限制,简称限重。
现在有 \(q\) 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。
输入格式
第一行有两个用一个空格隔开的整数 $ n,m$,表示 \(A\) 国有 $ n$ 座城市和 \(m\) 条道路。
接下来 \(m\) 行每行三个整数 \(x, y, z\),每两个整数之间用一个空格隔开,表示从 $x $ 号城市到 $ y $ 号城市有一条限重为 \(z\) 的道路。
注意: \(x \neq y\),两座城市之间可能有多条道路 。
接下来一行有一个整数 \(q\),表示有 \(q\) 辆货车需要运货。
接下来 \(q\) 行,每行两个整数 \(x,y\),之间用一个空格隔开,表示一辆货车需要从 \(x\) 城市运输货物到 \(y\) 城市,保证 \(x \neq y\)
输出格式
共有 \(q\) 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。
如果货车不能到达目的地,输出 \(-1\)。
样例输入 #1
4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3
样例输出 #1
3
-1
3
提示
对于 \(30\%\) 的数据,\(1 \le n < 1000\),\(1 \le m < 10,000\),\(1\le q< 1000\);
对于 \(60\%\) 的数据,\(1 \le n < 1000\),\(1 \le m < 5\times 10^4\),\(1 \le q< 1000\);
对于 \(100\%\) 的数据,\(1 \le n < 10^4\),\(1 \le m < 5\times 10^4\),$1 \le q< 3\times 10^4 $,\(0 \le z \le 10^5\)。
解析
因为我们想要经过的最小边最大,那么不妨构造一个最大生成树(建议使用克鲁斯卡尔算 法),这样每条边都能尽可能大
然后问题转换为树上查询,同样利用倍增法求\(x->LCA,y->LCA\)路径中的最小边,也是可以预处理的
不过问题不保证树联通,需要判断是否有解
克鲁斯卡尔的优势就体现出来了,我们已经处理了并查集,如果两点祖先不同就直接判断为无解
核心代码如下(码风十分奇怪)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
struct road
{
ll s,t,w;
}r[200005];
struct node
{
ll to,next,w;
}a[200005];
ll n,m,t,k,x,y,fa2[100005],h[100005],fa[100005][33],f[100005][33],dep[100005],lg[100005];
bool cmp(road x,road y)
{
return x.w>y.w;
}
void add(int x,int y,int z)
{
t++;
a[t].to=y;
a[t].w=z;
a[t].next=h[x];
h[x]=t;
}
int find(int x)
{
if(fa2[x]==x)return x;
return fa2[x]=find(fa2[x]);
}
void dfs(long long x,long long fn)
{
fa[x][0]=fn;
dep[x]=dep[fn]+1;
for(int i=1;i<=31;i++)
{
fa[x][i]=fa[fa[x][i-1]][i-1];
f[x][i]=min(f[x][i-1],f[fa[x][i-1]][i-1]);//f数组表示x到fa[x][i]路径的最小值
}
for(int i=h[x];i;i=a[i].next)
{
if(a[i].to!=fn)
{
f[a[i].to][0]=a[i].w;
dfs(a[i].to,x);
}
}
}
int lca(int x,int y)
{
if(dep[x]<dep[y])
{
swap(x,y);
}
while(dep[x] > dep[y])
{
x = fa[x][lg[dep[x]-dep[y]] - 1];
}
if(x==y)
{
return x;
}
for(int k = lg[dep[x]] - 1; k >= 0;k--)
{
if(fa[x][k] != fa[y][k])
{
x = fa[x][k], y = fa[y][k];
}
}
return fa[x][0];
}
int work(int x,int y)//求解x到y路径的最小值,保证y是x祖先
{
ll ans=1e9,deph=dep[x]-dep[y];
while(deph!=0)
{
ll t=lg[deph]-1;
ans=min(ans,f[x][t]);
x=fa[x][t];
deph=dep[x]-dep[y];
}
return ans;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
fa2[i]=i;
lg[i] = lg[i-1] + (1 << lg[i-1] == i);
}
for(int i=1;i<=m;i++)
{
cin>>r[i].s>>r[i].t>>r[i].w;
}
sort(r+1,r+m+1,cmp);//克鲁斯卡尔
int k=n-1;
for(int i=1;i<=m;i++)
{
if(k==0)break;
if(find(r[i].s)!=find(r[i].t))
{
add(r[i].s,r[i].t,r[i].w);
add(r[i].t,r[i].s,r[i].w);
fa2[find(r[i].s)]=find(r[i].t);
k--;
}
}
for(int i=1;i<=n;i++)
{
if(find(i)==i)
{
dfs(i,0);
}
}
cin>>k;
for(int i=1;i<=k;i++)
{
cin>>x>>y;
if(find(x)!=find(y))
{
cout<<-1<<endl;
continue;
}
int lcah=lca(x,y);
cout<<min(work(x,lcah),work(y,lcah))<<endl;
}
}
Duck006[DuckOI]Kill the Duck
原题展现
温馨提示
Duck非常不要脸,单推自己的题
后来发现其实有好多一样的题
- 贪玩的小孩
- HDU 2586 How far away?
题目描述
XCR是世界名列前茅的OIer,今天在打模拟赛。
他已经AC了前四道题,准备暴切第五题,看着这个题面,突然发现不太对....
他一看五道题的名字
\mathtt{\color{red}{C}\color{black}{ount\;the\;Number\;of\;Dance\;Schemes}}\\
\mathtt{\color{red}{R}\color{black}{elaxing\;Time }}\\
\mathtt{\color{red}{A}\color{black}{n\; Easy\;Problem}}\\
\mathtt{\color{red}{K}\color{black}{ill\;the\;Duck}}\\
\mathtt{\huge{\color{red}{XCRAK}}}
\]
XCR十分生气,想要杀了DengDuck
DengDuck跑到了一个有\(n\)个结点,\(n-1\)条边的树上
这个树的每个边都是无向的,都有边权
XCR现在有\(m\)次询问,第\(i(1 \leq i \leq m)\)次给出两个正整数\(x_i\)和\(y_i\),含义如下
DengDuck 在点 \(x_i(1 \leq x_i \leq n)\) 上,XCR在点 \(y_i(1 \leq y_i \leq n)\) 上
对于每次询问,请问XCR离DengDuck的距离是多少?
输入格式
第一行一个整数\(n\)
接下来\(n-1\)行每行三个正整数分别表示一条边的起点,终点,边权
第\(n+1\)行一个正整数\(m\)
接下来\(m\)行每行两个正整数\(x_i\)和\(y_i\)
输出格式
有\(m\)行,每行一个正整数,表示DengDuck和XCR的距离
样例输入 #1
3
1 2 3
2 3 4
2
1 2
1 3
样例输出 #1
3
7
样例输入 #2
3
1 3 10
1 2 13
5
1 1
2 2
3 1
2 1
1 3
样例输出 #2
0
0
10
13
10
样例输入 #3
14
5 7 12
7 11 15
5 14 12
14 3 17
7 1 19
14 4 14
1 12 16
1 6 16
12 9 19
9 10 10
7 2 11
4 8 10
2 13 14
17
6 11
14 14
13 11
6 10
12 6
8 7
9 9
10 11
13 10
1 4
2 12
13 4
2 7
2 1
12 2
10 11
4 7
样例输出 #3
50
0
40
61
32
48
0
79
89
57
46
63
11
30
46
79
38
提示
对于一定的数据 | \(n,m\)的范围 | 特殊限制 |
---|---|---|
前\(5\%\)的数据 | \(1~20\) | 无 |
前\(20\%\)的数据 | \(1~3000\) | 无 |
另外的\(5\%\)的数据 | \(1~3000\) | \(m=1\) |
所有数据 | \(1~100000\) | 无 |
解析
预处理出\(dis_i\)表示点\(i\)到根\(1\)的距离,答案是\(dis_x+dis_y-2dis_{lca(x,y)}\)
非常容易证明
代码如下
#include <bits/stdc++.h>
using namespace std;
int n, k, b[1000005], x, y, z, tot, h[500005], len[500005], fa[500005][33], dep[500005], lg[500005],
f[1000005], ans;
struct node {
int to, next, w;
} a[1000005];
void dfs(long long x, long long fn, long long l) {
fa[x][0] = fn;
dep[x] = dep[fn] + 1;
len[x] = l;
for (int i = 1; i <= 31; i++) {
fa[x][i] = fa[fa[x][i - 1]][i - 1];
}
for (int i = h[x]; i; i = a[i].next) {
if (a[i].to != fn) {
dfs(a[i].to, x, l + a[i].w);
}
}
}
int lca(int x, int y) {
if (dep[x] < dep[y]) {
swap(x, y);
}
while (dep[x] > dep[y]) {
x = fa[x][lg[dep[x] - dep[y]] - 1];
}
if (x == y) {
return x;
}
for (int k = lg[dep[x]] - 1; k >= 0; k--) {
if (fa[x][k] != fa[y][k]) {
x = fa[x][k], y = fa[y][k];
}
}
return fa[x][0];
}
void add(int x, int y, int z) {
++tot;
a[tot].to = y;
a[tot].next = h[x];
a[tot].w = z;
h[x] = tot;
}
void answer(int x, int fn) {
for (int i = h[x]; i; i = a[i].next) {
if (a[i].to != fn) {
answer(a[i].to, x);
f[x] += f[a[i].to];
}
}
ans = max(ans, f[x]);
}
int main() {
cin >> n;
for (int i = 1; i <= n; ++i) {
lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
}
for (int i = 1; i <= n - 1; i++) {
cin >> x >> y >> z;
add(x, y, z);
add(y, x, z);
}
dfs(1, 0, 0);
cin >> k;
for (int i = 1; i <= k; i++) {
cin >> x >> y;
int t = lca(x, y);
cout << len[x] + len[y] - 2 * len[t] << endl;
}
}
浅谈倍增法求解LCA的更多相关文章
- HDU 2586 倍增法求lca
How far away ? Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)To ...
- 倍增法求LCA
倍增法求LCA LCA(Least Common Ancestors)的意思是最近公共祖先,即在一棵树中,找出两节点最近的公共祖先. 倍增法是通过一个数组来实现直接找到一个节点的某个祖先,这样我们就可 ...
- 倍增法求lca(最近公共祖先)
倍增法求lca(最近公共祖先) 基本上每篇博客都会有参考文章,一是弥补不足,二是这本身也是我学习过程中找到的觉得好的资料 思路: 大致上算法的思路是这样发展来的. 想到求两个结点的最小公共祖先,我们可 ...
- LCA—倍增法求解
LCA(Least Common Ancestors) 即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先. 常见解法一般有三种 这里讲解一种在线算法-倍增 首先我们定义fa[u][j ...
- 【数据结构】浅谈倍增求LCA
思路 运用树上倍增法可以高效率地求出两点x,y的公共祖先LCA 我们设f[x][k]表示x的2k辈祖先 f[x][0]为x的父节点 因为从x向根节点走2k 可以看成从x走2k-1步 再走2k-1步 所 ...
- 树上倍增法求LCA
我们找的是任意两个结点的最近公共祖先, 那么我们可以考虑这么两种种情况: 1.两结点的深度相同. 2.两结点深度不同. 第一步都要转化为情况1,这种可处理的情况. 先不考虑其他, 我们思考这么一个问题 ...
- 倍增法求LCA(最近公共最先)
对于有根树T的两个结点u.v,最近公共祖先x=LCA(u,v)表示一个结点x,满足x是u.v的祖先且x的深度尽可能大. 如图,根据定义可以看出14和15的最近公共祖先是10, 15和16的最近公共 ...
- 浅谈倍增LCA
题目链接:https://www.luogu.org/problemnew/show/P3379 刚学了LCA,写篇blog加强理解. LCA(Least Common Ancestors),即最近公 ...
- 在线倍增法求LCA专题
1.cojs 186. [USACO Oct08] 牧场旅行 ★★ 输入文件:pwalk.in 输出文件:pwalk.out 简单对比时间限制:1 s 内存限制:128 MB n个被自 ...
随机推荐
- Qt 实现配置 OpenCV 环境,并实现打开图片与调用摄像头
一.说明 所用QT版本:5.9.1 电脑配置:win10,64位系统 调用的是编译好的:OpenCV-MinGW-Build-4.1.0(稍后放链接) 在大学期间,由于项目需求需要用到QT+openc ...
- 递归函数求n!
#include<iostream> using namespace std; int main() { int n; cin>>n; int jieceng(int); co ...
- YC-Framework版本更新:V1.0.6
分布式微服务框架:YC-Framework版本更新V1.0.6!!! 本文主要内容: V1.0.6版本更新主要内容 V1.0.6版本更新主要内容介绍 一.V1.0.6版本更新主要内容 1.系统例子覆盖 ...
- Java基础语法01——变量与运算符
本文是对Java基础语法的第一部分的学习,包括注释:标识符的命名规则与规范:变量的数据类型分类以及转换:以及六种运算符(算术.赋值.比较.逻辑.三元和位运算符).
- 1903021116—吉琛—Java第六周作业—类的定义
项目 内容 课程班级博客链接 19信计班 这个作业要求链接 第六周作业链接 java面向对象的概念和定义 博客名称 学号-姓名-Java第六周作业-题目自拟 要求 每道题要有题目,代码(使用插入代码, ...
- Python打印表格
使用Python在终端打印表格 import prettytable table = PrettyTable(['Title1', 'Title2', 'Title3']) table.add_row ...
- Golang Zap日志
Zap日志解析 Config.yaml zap: level: 'info' #日志级别 format: 'console' #输出的级别,有console和json prefix: '[cateri ...
- C#/VB.NET 获取Excel中图片所在的行、列坐标位置
本文以C#和vb.net代码示例展示如何来获取Excel工作表中图片的坐标位置.这里的坐标位置是指图片左上角顶点所在的单元格行和列位置,横坐标即顶点所在的第几列.纵坐标即顶点所在的第几行.下面是获取图 ...
- sentinel基础概念及使用
点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人. 文章不定期同步公众号,还有各种一线大厂面试原题.我的学习系列笔记. 什么是sentinel sentinel是Spring Cloud Al ...
- XCTF练习题---MISC---normal_png
XCTF练习题---MISC---normal_png flag:flag{B8B68DD7007B1E406F3DF624440D31E0} 解题步骤: 1.观察题目,下载附件 2.拿到手以后发现是 ...