前言

我们在做树形题和图论题时常常遇到这样的问题:要求求出树上两点间的最近公共祖先(LCA),这时我们该怎么办?

思路一:暴力爬爬爬……

很容易想到让两个点都往上爬,啥时候相遇了就是他们的最近公共祖先。

但是这实在是太慢了啊!怎么办呢?

思路二:倍增思想

倍增思想来源于数学。

首先,可以证明任意整数可以写成二进制形式(废话)。

然后,可以证明向上爬的层数不是正整数就是0(又是废话)。

最后,如果每次爬的层数是其二进制中的每一位,即成二的几次方的往上爬,可以证明一定能爬到他们的LCA!(恍然大悟)

如何实现

你比方说,我要向上爬个\(n\)层。这个\(n\)比如说是\(114514\)

那他的二进制就是:

\(11 011 111 101 010 010\)

我们怎么爬?

既然是成二的几次方的爬,那我们从最左边开始吧!

我们可以发现,114514的二进制最左边一位是1,代表了\(2^{17}\),二的17次方。

维护一个计数器,记录我们爬了多少。

可以发现,爬了\(2^{17}\)层后,没有超过114514,那就看下一位:\(2^{16}\)

如果这一位爬了,也是不越界的,那就继续爬

但是\(2^{15}\)这一位,如果爬了就越界了!所以此时我们要舍弃这一位,继续去看下一位能不能爬。

这就大大减少了我们向上爬的时间,这就是倍增思想。

LCA代码实现

请看代码,这同时是这道题的AC代码:

#include<bits/stdc++.h>
using namespace std;
int read(){
int x=0;
char c=getchar();
while(c>'9'||c<'0'){
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<1)+(x<<3)+(c^'0');
c=getchar();
}
return x;
}//快读不用管
int n,m,s,x,y;
vector<int>tree[500001];//树
int dep[500001];//深度
int f[500001][21];//f[i][j]表示i节点的2的j次方祖先是谁
void dfs(int now,int d,int father){
dep[now]=d;//记录深度
f[now][0]=father;//爸爸是爸爸
for(int i=1;(1<<i)<=dep[now];i++){
f[now][i]=f[f[now][i-1]][i-1];//预处理祖先,数学原理:2^i=2^(i-1)+2^(i-1)
}
for(int i=0;i<tree[now].size();i++){//遍历相连的边
if(tree[now][i]!=father)dfs(tree[now][i],d+1,now);//如果不是爸爸,就dfs
}
}
int lca(int u,int v){
int temp;
if(dep[u]<dep[v])swap(u,v);
temp=dep[u]-dep[v];//记录深度差
for(int i=0;(1<<i)<=temp;i++){
if((1<<i)&temp){
u=f[u][i];
}
}//这里是先把那个低一点的爬到和高一点的齐平
if(u==v)return u;
for(int i=(int)(log(n)/log(2));i>=0;i--){//一位位爬,这里这个log的用法用到了对数函数换底公式,不用理解太透
if(f[u][i]!=f[v][i]){//如果祖先不同,因为超限就是未定义,未定义就是0,会祖先相同所以可以用来防止超限
u=f[u][i];//爬
v=f[v][i];
}
}
return f[u][0];//最后两个的爸爸就是LCA
}
int main(){
n=read();//节点数
m=read();//询问个数
s=read();//根节点
for(int i=1;i<n;i++){
x=read();
y=read();
tree[x].push_back(y);
tree[y].push_back(x);
//邻接表建树也不用说
}
dfs(s,1,0);//注意!首先一边DFS进行预处理
for(int i=1;i<=m;i++){
x=read();
y=read();
printf("%d\n",lca(x,y));//求LCA
}
return 0;
}

算法之倍增和LCA:论点与点之间的攀亲戚的更多相关文章

  1. [算法]树上倍增求LCA

    LCA指的是最近公共祖先(Least Common Ancestors),如下图所示: 4和5的LCA就是2 那怎么求呢?最粗暴的方法就是先dfs一次,处理出每个点的深度 然后把深度更深的那一个点(4 ...

  2. [算法模板]倍增求LCA

    倍增LCA \(fa[a][i]\)代表a的第\(2^{i}\)个祖先. 主体思路是枚举二进制位,让两个查询节点跳到同一高度然后再向上跳相同高度找LCA. int fa[N][21], dep[N]; ...

  3. 倍增求LCA算法详解

    算法介绍: 看到lca问题(不知道lca是什么自(bang)行(ni)百度),不难想到暴力的方法: 先把两点处理到同一深度,再让两点一个一个祖先往上找,直到找到一个相同的祖先: 这么暴力的话,时间复杂 ...

  4. Codeforces 519E A and B and Lecture Rooms [倍增法LCA]

    题意: 给你一棵有n个节点的树,给你m次询问,查询给两个点,问树上有多少个点到这两个点的距离是相等的.树上所有边的边权是1. 思路: 很容易想到通过记录dep和找到lca来找到两个点之间的距离,然后分 ...

  5. 【题解】POJ 3417 Network(倍增求LCA+DP+树上差分)

    POJ3417:http://poj.org/problem?id=3417 思路 我们注意到由“主要边”构成一颗树 “附加边”则是非树边 把一条附加边(x,y)加入树中 会与树上x,y之间构成一个环 ...

  6. 【模板时间】◆模板·I◆ 倍增计算LCA

    [模板·I]LCA(倍增版) 既然是一篇重点在于介绍.分析一个模板的Blog,作者将主要分析其原理,可能会比较无趣……(提供C++模板) 另外,给reader们介绍另外一篇非常不错的Blog(我就是从 ...

  7. 倍增求LCA学习笔记(洛谷 P3379 【模板】最近公共祖先(LCA))

    倍增求\(LCA\) 倍增基础 从字面意思理解,倍增就是"成倍增长". 一般地,此处的增长并非线性地翻倍,而是在预处理时处理长度为\(2^n(n\in \mathbb{N}^+)\ ...

  8. [学习笔记] 树上倍增求LCA

    倍增这种东西,听起来挺高级,其实功能还没有线段树强大.线段树支持修改.查询,而倍增却不能支持修改,但是代码比线段树简单得多,而且当倍增这种思想被应用到树上时,它的价值就跟坐火箭一样,噌噌噌地往上涨. ...

  9. 树上倍增求LCA(最近公共祖先)

    前几天做faebdc学长出的模拟题,第三题最后要倍增来优化,在学长的讲解下,尝试的学习和编了一下倍增求LCA(我能说我其他方法也大会吗?..) 倍增求LCA: father[i][j]表示节点i往上跳 ...

  10. 【倍增】洛谷P3379 倍增求LCA

    题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每 ...

随机推荐

  1. 《吐血整理》高级系列教程-吃透Fiddler抓包教程(29)-Fiddler如何抓取Android7.0以上的Https包-终篇

    1.简介 上一篇宏哥介绍的Xposed是一款可以在不修改APK的情况下影响程序运行的框架.可以编写并加载自己编写的插件app,实现对目标apk的注入.拦截等.一般研究移动安全的都会使用Xposed. ...

  2. 13-ORM-更新&删除

    一.更改单个数据 修改单个实体的某些字段 1.查: - 通过get()得到要修改的实体对象 2.改: - 通过对象属性的=的方式修改数据 3.保存 - 通过对象.save()保存数据     二.批量 ...

  3. SQL Server-表结构的操作

    1.修改表的字段的数据类型 alter table [File_Info] alter column Upload_Request_ID nvarchar(14) not null 2.添加表的字段并 ...

  4. python简单的tcp服务端

    1 #!/usr/bin/python 2 # -*- coding: UTF-8 -*- 3 # 文件名:tcpserver.py 4 5 import socket 6 import time 7 ...

  5. node 创建服务器方法

    方法一 let http = require('http') let httpserver = http.createServer(function(req,res){ res.writeHead(2 ...

  6. Java安全之动态加载字节码

    Java字节码 简单说,Java字节码就是.class后缀的文件,里面存放Java虚拟机执行的指令. 由于Java是一门跨平台的编译型语言,所以可以适用于不同平台,不同CPU的计算机,开发者只需要将自 ...

  7. CentOS 8 离线安装 podman 解决方法

    CentOS 8 系统中如果没有安装Podman的话,想要离线安装会比较麻烦,因为podman依赖的包比较多,从网上一个一个下载会很繁琐,也容易出错. 这里介绍一种曲线救国的方式来离线安装. 首先分享 ...

  8. 2022春每日一题:Day 14

    题目:字符串归类 发现字符串长度总数不大,因此把每个字符串有的字母分离,存放到桶中,再枚举合并即可,时间复杂度O(len) 赛时代码: #include <cstdio> #include ...

  9. tcp网络交互的理解 以及代码实现

    服务端 import socketserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server.bind(("127.0. ...

  10. 【每日一题】【回溯】2021年12月29日-93. 复原 IP 地址

    有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔. 例如:"0.1.2.201" 和 "192.1 ...