最近公共祖先LCA倍增算法

news/2024/7/15 17:32:18 标签: 算法, 树结构, 图论, acm竞赛

LCA定义

LCA(Least Common Ancestors):最近公共祖先即两个结点最近的公共祖先

在这里插入图片描述

由上图可以看到 5号结点和7号结点的LCA为3号结点,9号结点和10号结点的LCA为7号结点。

一般算法

首先可以将两个结点统一到相同深度,然后一起向上一步一步走,直到他们踩到相同点,则该点为他们的LCA 。

(树的深度:与根节点的距离则为树的深度,根节点的深度为0或者1,根据个人喜好设置)

复杂度分析

因为每次向上走一步,最多n个结点深度最大也就为n,所以最多也就走n步,复杂度为O(n)

倍增算法

既然每次走一步太慢了,那么能不能快一点走呢?
每次走k步呢?

Sure当然可以But!

首先给出答案,每次走2的k次幂步 1、2、4、8、16…,那么有个问题我是从大步向小步走呢?还是小步向大步走呢?eg5=2^0+2 ^1+2 ^2-2 ^1 如果从小—>大走那么需要回退也就是减去2 ^1这个过程,那么从大向小走呢5= 2 ^2+2 ^1 可以完美解决回退这个问题!

通过上面分析,我们每次走2的k次幂步,并且k是依次递减的

有一个问题,每次走2的k次幂,那么我怎么知道图中从x结点走了2的k次幂步后的结点是哪一个呢???

通过预处理可以求出第i个结点 走了2^j步后的结点编号

倍增算法的核心

递推公式f(i,j)=f(f(i,j-1),j-1)
f(i,j)表示第i个结点走2^j步后的结点
那么也就是第f(i,j-1)这个结点向前走2^j-1步两者可以达到同一效果!
eg: 2^4=2 ^3+2 ^3=16
也就是我只要求出f(2,3)就可以递推求出f(2,4)

通过预处理后,我可以求得任意一点走2^k后的结点编号,即f(i,j)

完整代码

(详解在注释里)

#include <bits/stdc++.h>
#define ll long long

using namespace std;

const int maxn=1e6+10;
int n,m,r,ecnt;
int head[maxn],depth[maxn],f[maxn][30];//这个30根据最大深度而来如果给出n个结点那么2^k=n  k=log2 n 所以得出30
//链式前向星存图
struct edge
{
    int u,v,next;
} E[maxn];
//添加边
void add(int u,int v)
{
    E[++ecnt].u=u;
    E[ecnt].v=v;
    E[ecnt].next=head[u];
    head[u]=ecnt;
}

void dfs(int u,int d)
{
    depth[u]=d;//确定每个结点深度
    for(int i=head[u]; i; i=E[i].next)//链式前向星的模板遍历
    {
        int v=E[i].v;
        if(!depth[v])
        {
            dfs(v,d+1);
            f[v][0]=u;//v结点向前走1步为u结点
        }
    }
}

int  LCA(int x,int y)
{
    if(depth[x]<depth[y]) swap(x,y); //假设x为深度最大的点
    //将x和y的深度统一为y的深度 20根据n的大小而来
    for(int i=20; i>=0; i--)
    {
        if(depth[x]-(1<<i)>=depth[y]) //每次向上走 2的i次幂步
        {
            x=f[x][i];
        }
    }
    if(x==y) return x; //如果已经在同一结点那么不需要在向上走了,直接返回x为答案
    //一起向上走
    for(int i=20; i>=0; i--)//从大步-->小步
    {
        if(f[x][i]!=f[y][i]) //只要不在一个结点就向上走
        {
            x=f[x][i];//x移动2^i步
            y=f[y][i];//y移动2^i步
        }
    }
    //因为f[x][i]==f[y][i]时一值没有处理,x和y一直没有移动,只有不相等时才移动所以最后差一步为答案,不懂的可以动手模拟一下
    return f[x][0]; //最后在向上走一步为答案

}


int main()
{

    cin>>n>>m>>r;//读入n个结点 m次询问 r为根节点

    int u,v;
    for(int i=1; i<=n-1; i++)
    {
        scanf("%d%d",&u,&v);
        //不要忘记添加双向边
        add(u,v);
        add(v,u);
    }
    f[r][0]=r;//根结点向前走2^k步仍为根节点
    dfs(r,1);//深搜求出每个结点的深度,以及f(i,0)走一步所对应的结点是谁
    //预处理,倍增的核心
    for(int i=1; (1<<i)<=n; i++) //1<<i 为2的i次幂步
    {
        for(int j=1; j<=n; j++)//遍历n个结点,i和j的顺序不要乱
        {
            f[j][i]=f[f[j][i-1]][i-1];
        }
    }
    //m次询问输出结果
    for(int i=1; i<=m; i++)
    {
        scanf("%d%d",&u,&v);
        cout<<LCA(u,v)<<endl;
    }

    return 0;
}

根据最开始的图举例

17 ->10(到达同一深度)->7->3(最后在走一步为答案)
11->等待到达同一深度->5->3

复杂度分析

显然以前需要走n步,现在只需要log2n 步 所以复杂度由O(n)–>O(logn)


http://www.niftyadmin.cn/n/860318.html

相关文章

IDEA中创建WEB项目

一、创建WEB项目 一、创建普通Java项目 二、创建WEB项目 1、点击Add Framework Support 2、选中Web Application 3、成功以后会出现web的包(带蓝色的点的包) 三、配置Tomcat 1、点击Add Configuration 2、点击左上角号 3、下拉找到Tomcat Server Local 点击OK 4、点击Con…

J2EE的来龙去脉

B/S B/S定义:Broswer-Server模式,是浏览器和服务器模式,是互联网的核心 B/S执行流程 用户通过浏览器向发送数据,服务器主机回应数据,其中用户发送数据为Request,服务器回应数据为Response! J2EE J2ee定义:Java 2 Platform Enterprise Edition 指Java2企业版 利用B/S模式开发Web…

JSP因果分析

为什么要有JSP 首先通过servlet返回页面给用户,这些功能都是servlet来完成,页面的部署也是通过servlet来完成,那么页面的样式内容,统统都是servlet来写,servlet本身在代码层面就是java的一个类,如果在java类面写html那么相当费时费力!(看下图) 于是可以通过JSP(Java Server Pag…

ContentType作用

contentType可以设置浏览器相应文本的设置 package servlet;import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.annotation.*; import java.io.IOException;WebServlet(name "contentType", value "/contentType") public cla…

Servlet中请求转发与响应重定向的区别与联系

servlet想要实现servlet的跳转有两个方法getRequestDispatcher(“servlet名称”) sendRedirect(“工程名/servlet名称”) 区别 getRequestDispatcher()方法页面网址不会变为转发后的页面网址,并且浏览器向服务器的请求次数为1。 sendRedirect()方法页面网址改变为转发后的网址,…

JavaWeb三大作用域对象生命周期

HttpServletRequest-请求对象 生命周期:请求送往tomcat,请求对象被创建,当tomcat返回给浏览器,则对象被销毁 HttpSession-用户会话对象 生命周期:时常30分钟,并不是浏览器关闭就销毁,浏览器关闭只是session的Id被销毁,但是session对象并不改变 ServletContext-全局对象 生命周期…

JSTL来源配置应用

为什么要有JSTL 由于在使用jsp进行编写代码时需要用java的语言来书写逻辑,这个过程还不够简便,所以诞生了JSTL标签语言用于简化jsp的开发,以及代码的可维护性! JSTL的下载配置 放置在WEB-INF项目创建一个lib,必须要导入依赖 常用代码 <%taglib uri"http://java.sun.c…

Ajax起源应用

为什么要有Ajax 由于我们在使用web时候,每当执行一些交互操作的时候,页面总会发生变化,但是这个变化不能总是用户刷新以后才有,所以在web端需要一个局部刷新这个功能,所以有了Ajax(Asynchronous JavaScript And XML 异步的JavaScript和xml) Ajax使用流程 创建XmlHttpRequest对象…