LCA(Least Common Ancestors)

即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先。

常见解法一般有三种

这里讲解一种在线算法—倍增


首先我们定义fa[u][j]表示结点u的第2^j祖先

那么要怎么求出全部的fa数组呢

不难发现fa[u][0]就是u的父亲结点

这些父亲结点我们可以直接初始化

对于其他结点则有

fa[u][j]=fa[ fa[u][j-1] ] [j-1]

什么意思呢

u的第2^(j-1)祖先的第2^(j-1)祖先 就是u的第2^j祖先(有点像快速幂那样分解)


预处理各节点深度+初始fa[u][0]

void dfs(int u,int pa)
{
    dep[u]=dep[pa]+1;
    fa[u][0]=pa;
    for(int i=head[u];i;i=E[i].nxt)
    {
        int v=E[i].v;
        if(v!=pa) dfs(v,u);
    }
}

预处理fa数组

for(int i=1;(1<<i)<=n;i++)
for(int u=1;u<=n;u++)
fa[u][i]=fa[ fa[u][i-1] ][i-1];

预处理之后怎么求解LCA(u,v)呢

我么先假定dep[u]>dep[v]

则两点深度差 d=dep[u]-dep[v]

现在我们要做的是把u升到与v同样的深度

怎么做呢? 先贴代码

for(int i=0;(1<<i)<=d;i++)
if( (1<<i) & d ) x=fa[x][i];

对于任意一个d

我们都能将其分解为d=2^p1+2^p2+……2^pi

这可以用二进制实现

例如d=5 ,5的二进制是101

我们将其分解为100+1

而100的十进制是4,1的十进制是1!!!

4=2^2 1=2^0

5=2^2 +2^0

是不是好神奇!!!!(欢呼)

应用到这里

就是查询d的二进制哪些位是1

查询到第i位为1

我们就将u向上升2^i个深度

这样一定能升到与v同深度

(注意二进制最右边一位是第0号位)

if( (1<<i) & d )

这一段用来检查d的二进制下第i位是否为1


抬升到相同高度后就可以开始查询LCA了

同样先上代码

for(int i=(int)log(n);i>=0;i--)
{
    if(fa[x][i]!=fa[y][i])
    {
        x=fa[x][i];
        y=fa[y][i];
        }
    }
    return fa[x][0];
}

大体思路就是

从u和v最远的祖先开始

如果u的第2^i祖先等于 v的第2^i祖先,就不移动

否则u和v同时上移2^i个深度

最后u的父亲一定是u和v的LCA

(其实蒟蒻不是特别理解其中的玄学道理,但手动模拟了几组发现真的是这样)

妙啊~妙啊~


最后贴上一份完整代码

模板题传送门啦~啦~啦~

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cmath>
using namespace std;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return x*f;
}

void print(int x)
{
    if(x<0){putchar('-');x=-x;}
    if(x>9) print(x/10);
    putchar(x%10+'0');
}

int n,m,s;
int tot;
struct node{int v,nxt;}E[1000010];
int head[1000010];
int fa[5000010][25];
int dep[1000010];

void add(int u,int v)
{
    E[++tot].nxt=head[u];
    E[tot].v=v;
    head[u]=tot;
}

void dfs(int u,int pa)
{
    dep[u]=dep[pa]+1;
    fa[u][0]=pa;
    for(int i=head[u];i;i=E[i].nxt)
    {
        int v=E[i].v;
        if(v!=pa) dfs(v,u);
    }
}

int lca(int x,int y)
{
    if(dep[x]-dep[y]<0) swap(x,y);//将u设为深度较深的结点
    int d=dep[x]-dep[y];

    for(int i=0;(1<<i)<=d;i++)
    if( (1<<i) & d ) x=fa[x][i];//抬升

    if(x==y) return x;//抬升后x==y,则其LCA就是y(或此时的x)
    else
    {
        for(int i=(int)log(n);i>=0;i--)
        {
            if(fa[x][i]!=fa[y][i])
            {
                x=fa[x][i];
                y=fa[y][i];
            }
        }
        return fa[x][0];
    }
}

int main()
{
    n=read();m=read();s=read();
    for(int i=1;i<=n-1;i++)
    {
        int x=read(),y=read();
        add(x,y);add(y,x);
    }

    dfs(s,-1);
    //无根树转有根树,在这里调用是pa要设为-1

    //预处理fa数组
    for(int i=1;(1<<i)<=n;i++)
    for(int u=1;u<=n;u++)
    fa[u][i]=fa[ fa[u][i-1] ][i-1];

    while(m--)
    {
        int x=read(),y=read();
        int ans=lca(x,y);
        print(ans); printf("\n");
    }
    return 0;
} 

LCA—倍增法求解的更多相关文章

  1. LCA(最近公共祖先)——LCA倍增法

    一.前人种树 博客:最近公共祖先 LCA 倍增法 博客:浅谈倍增法求LCA 二.沙场练兵 题目:POJ 1330 Nearest Common Ancestors 代码: const int MAXN ...

  2. POJ - 1330 Nearest Common Ancestors(dfs+ST在线算法|LCA倍增法)

    1.输入树中的节点数N,输入树中的N-1条边.最后输入2个点,输出它们的最近公共祖先. 2.裸的最近公共祖先. 3. dfs+ST在线算法: /* LCA(POJ 1330) 在线算法 DFS+ST ...

  3. 浅谈倍增法求解LCA

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

  4. hdu2586 lca倍增法

    倍增法加了边的权值,bfs的时候顺便把每个点深度求出来即可 #include<iostream> #include<cstring> #include<cstdio> ...

  5. poj1470 LCA倍增法

    倍增法模板题 #include<iostream> #include<cstring> #include<cstdio> #include<queue> ...

  6. 最近公共祖先 LCA 倍增法

    [简介] 解决LCA问题的倍增法是一种基于倍增思想的在线算法. [原理] 原理和同样是使用倍增思想的RMQ-ST 算法类似,比较简单,想清楚后很容易实现. 对于每个节点u , ancestors[u] ...

  7. luogu3379 【模板】最近公共祖先(LCA) 倍增法

    题目大意:给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 整体步骤:1.使两个点深度相同:2.使两个点相同. 这两个步骤都可用倍增法进行优化.定义每个节点的Elder[i]为该节点的2^k( ...

  8. LCA - 倍增法去求第几个节点

    You are given a tree (an undirected acyclic connected graph) with N nodes, and edges numbered 1, 2, ...

  9. POJ 1330(LCA/倍增法模板)

    链接:http://poj.org/problem?id=1330 题意:q次询问求两个点u,v的LCA 思路:LCA模板题,首先找一下树的根,然后dfs预处理求LCA(u,v) AC代码: #inc ...

随机推荐

  1. xml报错 Parse Fatal Error :在实体引用中,实体名称必须紧跟在'&'后面

    修改jndi配置文件中的密码后,重启tomcat报错如下  实际问题是xml中默认’&’是非法字符,用     &   替代

  2. 【笔记】vue-cli 开发环境中跨域连接后台api(vue-resource 跨域post 请求)

    在vue-cli 项目中很多人会用到mock 数据(模拟数据),但是我觉得如果在真实的数据库交互中开发会更有安全感一些,所以查了一下百度很多人推荐的就是: 跨域! 跨域是什么概念?不同的主机名,同主机 ...

  3. Python+Selenium安装及环境配置

    一.Python安装 Window系统下,python的安装很简单.访问python.org/download,下载最新版本,安装过程与其他windows软件类似.记得下载后设置path环境变量,然后 ...

  4. 服务器大量的fin_wait1 状态长时间存在原因分析-1

    上文描述了在出现大量fin-wait-1出现的原因,占用的内存等,这里讲一下如何处理这种情况. 首先,fin发送之后,有可能会丢弃,那么发送多少次这样的fin包呢?fin包的重传,也会采用退避方式,在 ...

  5. [转]sysctl -P 报错解决办法

    问题症状 修改 linux 内核文件 #vi /etc/sysctl.conf后执行sysctl  -P 报错 error: "net.bridge.bridge-nf-call-ip6ta ...

  6. WEB安全_csrf攻击

    什么是xss跨站攻击? 正常访问服务器A,用户信任并在服务器A上登录操作,服务器A保存着用户的session数据,这时用户并没有退出服务器A的系统,然后访问危险服务器B,本来是用户请求服务器返回数据, ...

  7. 在eclispe的类中快速打出main方法

    在java类中快速打出main方法有两种途径: 1. 在新建类时,在New Java Class窗口中,将public static void main ( String[ ] args ) 前面打上 ...

  8. 【高并发简单解决方案】redis队列缓存 + mysql 批量入库 + php离线整合

    需求背景:有个调用统计日志存储和统计需求,要求存储到mysql中:存储数据高峰能达到日均千万,瓶颈在于直接入库并发太高,可能会把mysql干垮. 问题分析 思考:应用网站架构的衍化过程中,应用最新的框 ...

  9. IIS使用十大原则,(IIS过期时间,IIS缓存设置) 【转载】

    1. 自定义错误页虽然自定义错误页很简单,但只有少数管理员有效地利用了它.管理员可以在MMC中将HTTP错误信息映像到服务器上的绝对URL或是某个文件,更为详细的信息可以在这里找到.如果你嫌这太麻烦, ...

  10. 计算器(Ext)

    <html> <head> <title>计算器</title> <meta charset="UTF-8"> < ...