树上倍增求LCA

简介: 题目描述如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。输入格式第一行包含三个正整数 N,M,SN,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。接下来 N-1N−1 行每行包含两个正整数 x, yx,y,表示 xx 结点和 yy 结点之间有一条直接连接的边(数据保证可以构成树)。接下来 MM 行每行包含两个正整数 a, ba,b,表示询问 aa 结点和 bb 结点的最近公共祖先。

题目链接


题目描述


如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。


输入格式


第一行包含三个正整数 N,M,SN,M,S,分别表示树的结点个数、询问的个数和树根结点的序号。

接下来 N-1N−1 行每行包含两个正整数 x, yx,y,表示 xx 结点和 yy 结点之间有一条直接连接的边(数据保证可以构成树)。

接下来 MM 行每行包含两个正整数 a, ba,b,表示询问 aa 结点和 bb 结点的 最近公共祖先


输出格式


输出包含 MM 行,每行包含一个正整数,依次为每一个询问的结果。


输入输出


样例 1

输入

5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5


输出


4
4
1
4
4


说明/提示

对于 30% 的数据,N≤10,M≤10。

对于 70% 的数据,N≤10000,M≤10000。

对于 100% 的数据,N≤500000,M≤500000。

微信图片_20220608211303.jpg

PICTURE:/home/ldu/.config/tencent-qq//AppData/file//sendpix2.jpg

在进行倍增的时候,考虑跳的步长从大到小进行安排,并且求一下log来优化

一些小细节:

对于该题可以采取链式前向星建图(vector也无所谓),再加边的时候应该注意是双向边,而不是单向的边


代码详解:


一:初始化头结点为-1

void init(){
    for(int i=1;i<=n;i++) head[i] = -1;
}


二:对输入数据进行加边操作:

void add(int u,int v){
    e[cnt].u = u;
    e[cnt].v = v;
    e[cnt].next = head[u];
    head[u] = cnt ++;
}


三:对于里面的DFS操作,就是对某一个节点的2^j级祖先进行预处理,采用DFS的方式来进行递归,表示在数组里面就是fa[x][j] ,在求解的过程中还要递归处理节点的深度,对给出的树根,可以巧妙的处理为这颗树根的祖先节点是0这个点,然后他的深度是1,在递归的过程中cur表示当前节点,而rt表示这个节点的根节点(也就是祖先节点),然后就可以在递归的过程中,将这个点的深度depth[cur] = depth[fa] + 1。


然后下面的for循环处理fa数组,这个循环所能走的最大2^j级祖先最大的j可以对他预处理一个log,也就是下面的 cal 函数。处理完之后就可以遍历和这个节点相连的所有节点,当这个点不是这个节点的根节点也就是rt的时候,对他进行递归的DFS处理,此时在传递参数的时候父亲节点就是cur,当前节点就是与其相连的节点


void dfs(int cur,int rt){
    fa[cur][0] = rt,dep[cur] = dep[rt] + 1;/// the depth of the current node changed
    for(int i=1;i <= lg[dep[cur]];i++){
        fa[cur][i] = fa[fa[cur][i-1]][i-1];
    }
    for(int i=head[cur];~i;i = e[i].next){/// visit all linked nodes
        if(e[i].v != rt) dfs(e[i].v,cur);
    }
}


四:lca函数


我们在处理的过程中,默认x节点的深度比y深,所以说如果说x深度比y深度小,就直接交换两个节点,方便下次进行处理


然后一直遍历,当x深度一直大于y深度的时候,我们对x一直进行转移


然后我们在下面从大到小转移节点,同时对两个节点进行转移处理,从大到小的过程中我们对他处理的过程是:根据预先处理的log大小到0,来进行从大到小的跳转,在跳转的过程中,我们要避免讲两个点跳转到同一个节点,如果能跳转到的节点不相同我们就跳转,相同的情况下不进行跳转;


关于为什么从大到小:从大到小的过程中一定有一种方式能够将这个数唯一的表示出来,而且不存在所谓的“反悔”的情况


在最后返回的时候,返回fa[x][0]即为答案

int lca(int x,int y){
    if(dep[x] < dep[y]) swap(x,y);
    while(dep[x] > dep[y]) x = fa[x][lg[dep[x] - dep[y]] - 1];
    if(x == y) return y;
    /// big -> small
    for(int k = lg[dep[x]] - 1;k >= 0;k --){
        if(fa[x][k] != fa[y][k]){
            x = fa[x][k];
            y = fa[y][k];
        }
    }
    return fa[x][0];
}


有待完善

欢迎批评指正

版本一时间:2021-03-07-22:11


int n,m;
int root;
int head[maxn];
struct node{
    int u;
    int v;
    int next;
}e[maxn];
int dep[maxn];/// save the depth of every node
int fa[maxn][30];
int lg[maxn];
int cnt = 0;
void init(){
    for(int i=1;i<=n;i++) head[i] = -1;
}
void add(int u,int v){
    e[cnt].u = u;
    e[cnt].v = v;
    e[cnt].next = head[u];
    head[u] = cnt ++;
}
void dfs(int cur,int rt){
    fa[cur][0] = rt,dep[cur] = dep[rt] + 1;/// the depth of the current node changed
    for(int i=1;i <= lg[dep[cur]];i++){
        fa[cur][i] = fa[fa[cur][i-1]][i-1];
    }
    for(int i=head[cur];~i;i = e[i].next){/// visit all linked nodes
        if(e[i].v != rt) dfs(e[i].v,cur);
    }
}
void cal(){
    for(int i=1;i<=n;i++){
        lg[i] = lg[i-1] + (1 << lg[i-1] == i);/// 2 ^ lg[i-1] == i true + 1
    }
}
int lca(int x,int y){
    if(dep[x] < dep[y]) swap(x,y);
    while(dep[x] > dep[y]) x = fa[x][lg[dep[x] - dep[y]] - 1];
    if(x == y) return y;
    /// big -> small
    for(int k = lg[dep[x]] - 1;k >= 0;k --){
        if(fa[x][k] != fa[y][k]){
            x = fa[x][k];
            y = fa[y][k];
        }
    }
    return fa[x][0];
}
int main() {
    ///freopen("1.in","r",stdin);
    ///freopen("1.out","w",stdout);
    cin >> n >> m >> root;
    cal();
    init();
    for(int i=1;i<n;i++){
        int x = read,y = read;
        add(x,y);
        add(y,x);
    }
    dfs(root,0);
    for(int i=1;i<=m;i++){
        int x = read,y = read;
        printf("%d\n",lca(x,y));
    }
  return 0;
}/// ac_code


目录
相关文章
|
算法 C++
Leetcode第二十二题(括号生成)
这篇文章讨论了如何使用递归算法解决LeetCode第22题“括号生成”的问题,提供了两种C++的实现方法,目的是生成所有有效的括号组合。
246 0
Leetcode第二十二题(括号生成)
线性回归原理(二)
**线性回归与梯度下降简介:** 梯度下降是一种优化算法,常用于线性回归,模拟下山过程寻找函数最小值。在单变量线性回归中,以函数f(x)=x²为例,从初始点开始,每次迭代沿着负梯度(函数增快的方向相反)移动,通过学习率α控制步长。重复此过程,逐步逼近最小值x=0。在多变量情况下,梯度是一个向量,指向函数增长最快的方向。评估线性回归模型性能的指标有平均绝对误差(MAE)、均方误差(MSE)和均方根误差(RMSE),它们衡量预测值与实际值的差距,越小表示模型越准确。
《数字逻辑设计与计算机组成》一 2.8 设计实例
本节书摘来自华章出版社《数字逻辑设计与计算机组成》一 书中的第2章,第2.8节,作者:[美]尼克罗斯·法拉菲,更多章节内容可以访问云栖社区“华章计算机”公众号查看。
1930 0
|
8天前
|
Shell API 开发工具
Claude Code 快速上手指南(新手友好版)
AI编程工具卷疯啦!Claude Code凭借任务驱动+终端原生的特性,成了开发者的效率搭子。本文从安装、登录、切换国产模型到常用命令,手把手带新手快速上手,全程避坑,30分钟独立用起来。
2722 14
|
6天前
|
人工智能 开发工具 iOS开发
Claude Code 新手完全上手指南:安装、国产模型配置与常用命令全解
Claude Code 是一款运行在终端环境中的 AI 编程助手,能够直接在命令行中完成代码生成、项目分析、文件修改、命令执行、Git 管理等开发全流程工作。它最大的特点是**任务驱动、终端原生、轻量高效、多模型兼容**,无需图形界面、不依赖 IDE 插件,能够深度融入开发者日常工作流。
2208 4
|
21天前
|
人工智能 JSON 供应链
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
LucianaiB分享零成本畅用JVS Claw教程(学生认证享7个月使用权),并开源GeoMind项目——将JVS改造为科研与产业地理情报可视化AI助手,支持飞书文档解析、地理编码与腾讯地图可视化,助力产业关系图谱构建。
23553 13
畅用7个月无影 JVS Claw |手把手教你把JVS改造成「科研与产业地理情报可视化大师」
|
8天前
|
人工智能 JSON BI
DeepSeek V4-Pro 接入 Claude Code 完全实战:体验、测试与关键避坑指南
Claude Code 作为当前主流的 AI 编程辅助工具,凭借强大的代码理解、工程执行与自动化能力深受开发者喜爱,但原生模型的使用成本相对较高。为了在保持能力的同时进一步降低开销,不少开发者开始寻找兼容度高、价格更友好的替代模型。DeepSeek V4 系列的发布带来了新的选择,该系列包含 V4-Pro 与 V4-Flash 两款模型,并提供了与 Anthropic 完全兼容的 API 接口,理论上只需简单修改配置,即可让 Claude Code 无缝切换为 DeepSeek 引擎。
2007 1