Luogu P3379

题意:对于两个节点,寻找他们的最近公共祖先。

一个显而易见的解法是对于每一个节点我们都往上遍历一遍,记录下它每一个祖先,然后再从另一个节点出发,一步一步往上走,找到以前记录过第一个节点就是这两个节点的LCA

事实上在这样的数据规模下,这种解法的时间复杂度是让人无法接受的。

很容易发现,这样的解法慢在两个节点是一步一步往上走的,也许可以想个办法一次跳一大步?

于是我们引入了倍增法求LCA。

我们知道,对于任意一个整数数,都可以使用\(2^k+2^{k-1}+...+2^0\)来表示,这是不需要证明的,因为二进制能表示十进制的每一个数。

那么我们就可以每一次跳\(2^k\)步来优化这个算法。

还有一个问题就是:我们应该从大的开始跳还是从小的开始跳呢?

答案是从大的开始,因为如果从小的开始,就有可能出现需要回溯的情况。

举个例子,如果从小的开始,我们就会凑出\(5=1+2+...\),发现不对,然后回溯,凑出\(5=1+4\),但是如果从大的开始就可以直接凑\(5=4+1\),只要判断一下加了这个数比要求的数要大就可以不要这个,取更小的。

但是我们会发现在做这件事之前,我们必须要知道两个节点的深度以便跳步,并且还需要预处理每一个节点的\(2^k\) 级的祖先。

一个DFS就可以轻松完成这个任务。

void add(int sta,int to)
{
//链式前向星存树
edge[++cnt].to=to;
edge[cnt].next=head[sta];
head[sta]=cnt;
//head[i]表示以sta为起点的最后一条边的编号
//edge[i].next表示与它起点相同的上一条边的编号
}//如果没有看懂可以寻找其他博主的资料
void dfs(int fa,int now)
{
deepth[now]=deepth[fa]+1;//记录深度
ant[now][0]=fa;//ant[now][i]表示now节点的2^i级祖先
for (int i=1;(1<<i)<=deepth[now];i++)
ant[now][i]=ant[ant[now][i-1]][i-1];
//关键的一步:now的2^i级祖先就是now的2^(i-1)级祖先的2^(i-1)级祖先
//理由如下:2^(i-1)+2^(i-1)=2*2^(i-1)=2^i
for (int i=head[now];i!=0;i=edge[i].next)
if (edge[i].to!=fa) dfs(now,edge[i].to);
//判断用于防止往回走。
}

预处理完之后我们就可以开始寻找LCA了,为了方便,我们采用的方法是令x,y节点先跳到同一深度,再统一往上跳。

int lca(int x,int y)
{
if (deepth[x]<deepth[y])
swap(x,y);//方便操作,令x为深度较大的那一个
while (deepth[x]>deepth[y])
x=ant[x][lg[deepth[x]-deepth[y]]-1];
//这样最后一定会跳到同一深度,想一想为什么。
//lg[i]数组表示log(2,i)+1
if (x==y) return x;
for (int i=lg[deepth[x]];i>=0;i--)//开始往上跳
if (ant[x][i]!=ant[y][i]) x=ant[x][i],y=ant[y][i];
//值得注意的时,我们并不能直接跳到他们的公共祖先上,否则可能会导致误判。
//如果直接往上跳到公共祖先,可能会多跳了。
return ant[x][0];
}

最后是完整代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
struct data
{
int to,next;
}edge[500005<<1];
int cnt,head[500005],deepth[500005],ant[500005][21],lg[500005],n,m,s,x,y;
void add(int sta,int to)
{
//链式前向星存树
edge[++cnt].to=to;
edge[cnt].next=head[sta];
head[sta]=cnt;
//head[i]表示以sta为起点的最后一条边的编号
//edge[i].next表示与它起点相同的上一条边的编号
}
void dfs(int fa,int now)
{
deepth[now]=deepth[fa]+1;//记录深度
ant[now][0]=fa;//ant[now][i]表示now节点的2^i级祖先
for (int i=1;(1<<i)<=deepth[now];i++)
ant[now][i]=ant[ant[now][i-1]][i-1];
//关键的一步:now的2^i级祖先就是now的2^(i-1)级祖先的2^(i-1)级祖先
//理由如下:2^(i-1)+2^(i-1)=2*2^(i-1)=2^i
for (int i=head[now];i!=0;i=edge[i].next)
if (edge[i].to!=fa) dfs(now,edge[i].to);
//判断用于防止往回走。
}
int lca(int x,int y)
{
if (deepth[x]<deepth[y])
swap(x,y);//方便操作,令x为深度较大的那一个
while (deepth[x]>deepth[y])
x=ant[x][lg[deepth[x]-deepth[y]]-1];
//这样最后一定会跳到同一深度,想一想为什么。
//lg[i]数组表示log(2,i)+1
if (x==y) return x;
for (int i=lg[deepth[x]];i>=0;i--)//开始往上跳
if (ant[x][i]!=ant[y][i]) x=ant[x][i],y=ant[y][i];
//值得注意的时,我们并不能直接跳到他们的公共祖先上,否则可能会导致误判。
//如果直接往上跳到公共祖先,可能会多跳了。
return ant[x][0];
}
int main()
{
scanf("%d%d%d",&n,&m,&s);
for (int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
add(x,y);add(y,x);//暂时不确定父子关系,所以要存两条边。
}
dfs(0,s);
for (int i=1;i<=n;i++) lg[i]=lg[i-1]+(1<<lg[i-1]==i);
//用于求出log(2,i)+1的近似值,作常数优化
for (int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
return 0;
}

【Luogu P3379】LCA问题的倍增解法的更多相关文章

  1. 【原创】洛谷 LUOGU P3379 【模板】最近公共祖先(LCA) -> 倍增

    P3379 [模板]最近公共祖先(LCA) 题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询 ...

  2. 货车运输 noip2013 luogu P1967 (最大生成树+倍增LCA)

    luogu题目传送门! 首先,题目让我们求每个货车的最大运输量,翻译一下就是求路径上边权最小的边. 利用一下贪心思想可知,所有货车肯定都会尽量往大的边走. 进一步翻译,即为有一些小边货车根本不会走,或 ...

  3. 关于LCA的倍增解法的笔记

    emmmmm近日刚刚学习了LCA的倍增做法,写一篇BLOG来加强一下印象w 首先 何为LCA? LCA“光辉”是印度斯坦航空公司(HAL)为满足印度空军需要研制的单座单发轻型全天候超音速战斗攻击机,主 ...

  4. Luogu P3379 【模板】最近公共祖先(LCA)

    qwq 预处理出从$x$节点向上跳2i个节点的序号$p[x][i]$及节点深度$dpth[x]$, 寻找$lca$时,从$Max$(可能的最大深度)到0枚举$i$, 首先把较深的一个节点向上跳至深度相 ...

  5. 【luogu P3379 最近公共祖先】 模板

    题目链接:https://www.luogu.org/problemnew/show/P3379 倍增求lca,先存下板子,留个坑以后再填讲解. in 5 5 43 12 45 11 42 43 23 ...

  6. lca入门———树上倍增法(博文内含例题)

    倍增求LCA: father[i][j]表示节点i往上跳2^j次后的节点 可以转移为 father[i][j]=father[father[i][j-1]][j-1] 整体思路: 先比较两个点的深度, ...

  7. 最近公共祖先:LCA及其用倍增实现 +POJ1986

    Q:为什么我在有些地方看到的是最小公共祖先? A:最小公共祖先是LCA(Least Common Ancestor)的英文直译,最小公共祖先与最近公共祖先只是叫法不同. Q:什么是最近公共祖先(LCA ...

  8. 【洛谷】1852:[国家集训队]跳跳棋【LCA】【倍增?】

    P1852 [国家集训队]跳跳棋 题目背景 原<奇怪的字符串>请前往 P2543 题目描述 跳跳棋是在一条数轴上进行的.棋子只能摆在整点上.每个点不能摆超过一个棋子. 我们用跳跳棋来做一个 ...

  9. 最近公共祖先算法LCA笔记(树上倍增法)

    Update: 2019.7.15更新 万分感谢[宁信]大佬,认认真真地审核了本文章,指出了超过五处错误捂脸,太尴尬了. 万分感谢[宁信]大佬,认认真真地审核了本文章,指出了超过五处错误捂脸,太尴尬了 ...

随机推荐

  1. Ubuntu 14.04 sudo免密码的方法| sudo不需要密码

    Ubuntu 14.04 sudo免密码的方法| sudo不需要密码 cd /etc/sudoers.d sudo touch nopasswd4sudo sudo vi nopasswd4sudo ...

  2. (七)javac编译

    文章目录 1.基本格式 2.目标路径 2.1 缺省项 2.2 指定路径 2.2.1 全路径 2.2.2 相对路径 3.源文件 3.1 无第三方库 3.1.1 基本方法 3.1.2 添加目录 3.1.3 ...

  3. SpringCloud之Zuul高并发情况下接口限流(十二)

    高并发下接口限流技术gauva(谷歌的框架) MySql最大连接数3000: 原理:框架每秒向桶里放100个令牌,接口请求来了先去拿令牌,拿到令牌后才能继续向后走,否则不允许向后执行:当接口请求太频繁 ...

  4. Mybaits 源码解析 (六)----- 全网最详细:Select 语句的执行过程分析(上篇)(Mapper方法是如何调用到XML中的SQL的?)

    上一篇我们分析了Mapper接口代理类的生成,本篇接着分析是如何调用到XML中的SQL 我们回顾一下MapperMethod 的execute方法 public Object execute(SqlS ...

  5. 平时服务正常,突然挂了,怎么重启都起不来,查看日志Insufficient space for shared memory file 内存文件空间不足

    Java HotSpot(TM) 64-Bit Server VM warning: Insufficient space for shared memory file:   /tmp/hsperfd ...

  6. Spring Boot 使用@Scheduled定时器任务

    摘要: Spring Boot之使用@Scheduled定时器任务 假设我们已经搭建好了一个基于Spring Boot项目,首先我们要在Application中设置启用定时任务功能@EnableSch ...

  7. js中const,var,let区别

    1.const定义的变量不可以修改,而且必须初始化(常量) const b = 5 // 正确 // const b // 错误,必须初始化 // b = 4 // 错误,不可被修改 console. ...

  8. 获得shell的几种姿势

    windows提权 1.通过sqlmap连接mysql获取shell (1)直接连接数据库 sqlmap.py -d "mysql://root:123456@127.0.0.1:3306/ ...

  9. Matplotlib 设置

    # 导入相关模块 import matplotlib.pyplot as plt import numpy as np 设置 figure Matplotlib 绘制的图形都在一个默认的 figure ...

  10. Lost My Music:倍增实现可持久化单调栈维护凸包

    题目就是求树上每个节点的所有祖先中(ci-cj)/(dj-di)的最小值. 那么就是(ci-cj)/(di-dj)的最大值了. 对于每一个点,它的(ci,di)都是二维坐标系里的一个点 要求的就是祖先 ...