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. 最简破解-java代码热加载热部署IDEA插件JRebel

    如果经济实力允许的话,还是建议大家去购买收费版.支持原创作者,才能有更好的产品出现. 一.Jrebel插件介绍 JRebel一款帮助我们在开发过程中实现热加载的插件,目前来说,在IDEA中实现热加载最 ...

  2. day3,用户交互,input的应用

    1.与用户交互 输入:input() python2.x版本 input后面家的东西要声明输入的类型       >>> input(">>:")   ...

  3. 在vue中使用Ueditor

    今天研究的主角是:UEditor UEditor是由百度WEB前端研发部开发的所见即所得的开源富文本编辑器,具有轻量.可定制.用户体验优秀等特点. 版本有很多 我用的是:[1.4.3.3 PHP 版本 ...

  4. 关于ArcGIS的OBJECTID生成策略拙见

    目录 诉求SDEOBJECTIDArcMap编辑重置OBJECTID 诉求 非GIS专业的人员可能很难理解ArcSDE中的表OBJECTID的重要性,要么总想着自己动手去维护,要么就想直接忽略它,导致 ...

  5. (Java) JWT-TokenUtils

    package com.vcgeek.hephaestus.utils; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; imp ...

  6. 百度语音合成---前端vue项目

    ☞:官方文档 ☞:网页示例 具体步骤: 1.通过 socket.io 接收后端传过来的数据. 2.判断是否在播放声音.  如果没有则直接获取百度 token 播放声音 3.如果有,则存入数组.声音播放 ...

  7. ThinkPHP5.1 反序列化利用链

    笔记里直接复制出来的   1 composer直接获取框架代码   ➜  composer create-project --prefer-dist topthink/think tp5137 ➜  ...

  8. Vue使用antV G2制作看板

    工作中需要制作一个看板,选型选用antV G2进行开发. 由于项目前端是使用Vue,于是研究了antVG2在Vue中的使用. 1.安装antv/g2 npm install @antv/g2 --sa ...

  9. js 将base64转为图片

    var imgurl = response.data; $(".codeimg").attr('src','data:image/png;base64,'+imgurl); var ...

  10. Android常见内存泄露

    前言 对于内存泄漏,我想大家在开发中肯定都遇到过,只不过内存泄漏对我们来说并不是可见的,因为它是在堆中活动,而要想检测程序中是否有内存泄漏的产生,通常我们可以借助LeakCanary.MAT等工具来检 ...