算法:最近公共祖先(LCA)

news2024/12/25 9:12:53

有根树中,每一个点都有好几个祖先(在往根节点走的过程中遇到的都是它的祖先),一般化,把本身也称为它的祖先

对于两个点,离它们最近的一个公共祖先被称为最近公共祖先

法一:向上标记法(暴力做法)O(n)不常用

对于其中一个点,在走到根节点的过程中标记走过的点,然后另一个点开始往根节点走,走到第一个被标记过的点即为这两个点的最近公共祖先

法二:倍增法

预处理每个点向上走2^k步的节点,f[i,j]表示从i开始,向上走2^j步所能走到的节点(j大于等于0,j小于等于logn)

通过递推的方式来求

当j为0时,f(i,j)=i的父节点

当j大于0时,f(i,j)=f(f(i,j-1),j-1)

depth[i]表示深度

哨兵:如果从i开始跳2^j步会跳过根节点,那么f[i,j]=0,depth[0]=0

二进制拼凑:

比如已经有1,2,4,8,16

要想拼凑11,首先从大往小看,11小于16,不可行,11大于8,那么由于从大到小8是第一个出现,那么8可行,11减去8得3,第一个满足小于等于3的是2,所以2可行,3减2得1,第一个满足小于等于1的是1,所以1可行

利用这个思想可以把x和y跳到一个相同的位置上

x和y相差depth[x]-depth[y]步,用2的次幂拼凑出它们之间的距离

如果depth[f(x,k)]大于等于depth[y],那么表示跳2^k步之后到达的点还是在y的下面的,那么就可以作为拼凑的一部分,可以跳2^k步

当f(a,k)等于f(b,k)时,不一定到达的是最近的公共祖先,有可能到达的是最近公共祖先的上面一个点

当f(a,k)不等于f(b,k)时,说明还没有走到a,b的最近公共祖先

预处理O(nlogn)

查询 O(logn)

步骤:

1.先将两个点跳到同一层(距根节点的距离相同)

2.如果两个点此时不在同一个位置,那么让两个点同时往上跳,一直跳到它们的最近公共祖先的下一层(即它们的最近公共祖先的儿子节点)

例1:祖孙询问 

 

 

AC代码: 

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=40010,M=2*N;
int h[N],e[M],ne[M],idx;
int depth[N],fa[N][16];
int q[N];
int n,m;
void add(int a,int b){
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void bfs(int root){
    memset(depth,0x3f,sizeof depth);
    depth[0]=-1,depth[root]=0;
    int hh=0,tt=0;
    q[0]=root;
    while(hh<=tt){
        int t=q[hh++];
        for(int i=h[t];i!=-1;i=ne[i]){
            int j=e[i];
            if(depth[j]>depth[t]+1){
                depth[j]=depth[t]+1;
                q[++tt]=j;
                fa[j][0]=t;//j跳一步跳到t点,即t为j的父节点
                for(int k=1;k<=15;k++) fa[j][k]=fa[fa[j][k-1]][k-1];//预处理点j跳2^0,2^1,...2^15跳到哪个点
            }
        }
    }
}
int lca(int a,int b){
    if(depth[a]<depth[b]) swap(a,b);//使得a在b的下面
    for(int k=15;k>=0;k--){
        if(depth[fa[a][k]]>=depth[b]) a=fa[a][k];//如果a跳2^k步还在b的下面,那么就可以跳,哨兵的好处在于当跳2^k步后跳出根节点,那么depth为0
    }
    //此时a和b已经在同一层了,即距离根节点的距离是相同的
    if(a==b) return a;//如果a等于b,那么a就是a和b的最近公共祖先
    //否则a和b同时往上跳
    for(int k=15;k>=0;k--){
        //如果跳到的点不是同一个点,那么说明还没有跳到最近公共祖先
        //哨兵的第二个好处在于当跳出根节点时,跳到的点为0,那么两者就相同了
        if(fa[a][k]!=fa[b][k]){
            a=fa[a][k];
            b=fa[b][k];
        }
    }
    //此时a和b跳到了它们最近公共祖先的儿子节点
    return fa[a][0];
}
int main()
{
    cin>>n;
    int root=0;
    memset(h,-1,sizeof h);
    for(int i=0;i<n;i++){
        int a,b;
        cin>>a>>b;
        if(b==-1) root=a;
        else add(a,b),add(b,a);
    }
    bfs(root);
    cin>>m;
    while(m--){
        int a,b;
        cin>>a>>b;
        int p=lca(a,b);
        if(p==a) cout<<1<<endl;
        else if(p==b) cout<<2<<endl;
        else cout<<0<<endl;
    }
    return 0;
}

vector,queue版: 

#include<bits/stdc++.h>
#define endl '\n'
//#define int long long
using namespace std;
const int N=4e4+10,M=17;
int fa[N][M];
int depth[N];
int n,m;
vector<vector<int>>e(N);
queue<int>q;
void bfs(int root){
    memset(depth,0x3f,sizeof depth);
    depth[0]=-1,depth[root]=0;
    q.push(root);
    while(q.size()){
        int t=q.front();
        q.pop();
        for(auto v:e[t]){
            if(depth[v]>depth[t]+1){
                depth[v]=depth[t]+1;
                q.push(v);
                fa[v][0]=t;
                for(int k=1;k<M;k++) fa[v][k]=fa[fa[v][k-1]][k-1];
            }
        }
    }
}
int lca(int a,int b){
    if(depth[a]<depth[b]) swap(a,b);
    for(int k=16;k>=0;k--){
        if(depth[fa[a][k]]>=depth[b]) a=fa[a][k];
    }
    if(a==b) return a;
    for(int k=16;k>=0;k--){
        if(fa[a][k]!=fa[b][k]){
            a=fa[a][k];
            b=fa[b][k];
        }
    }
    return fa[a][0];
}
void solve() {
    cin>>n;
    int root;
    for(int i=0;i<n;i++){
        int a,b;
        cin>>a>>b;
        if(b==-1) root=a;
        else {
            e[a].push_back(b);
            e[b].push_back(a);
        }
    }
    bfs(root);
    cin>>m;
    while(m--){
        int x,y;
        cin>>x>>y;
        int l=lca(x,y);
        if(l==x) cout<<1<<endl;
        else if(l==y) cout<<2<<endl;
        else cout<<0<<endl;
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t=1;
//    cin>>t;
    while(t--) {
        solve();
    }
    return 0;
}

法三:Tarjan----离线求LCA O(n+m)(向上标记法的优化)

在线:对于每一个询问单独处理

离线:对于所有询问,全部存起来,一起处理,再一起输出

在深度优先遍历时,将所有点分三大类:(1)已经遍历过,且回溯过的点 (2)正在搜索的分支 (3)还未搜索到的点

tarjan在存储询问的时候,正反都会存一次,当lca(u,v)等于u,v其中一个时,lca会求两次,其它情况lca求一次,正反都存一次的目的是为了防止漏求lca

例:

AC代码: 

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
typedef pair<int,int>PII;
const int N=20010,M=2*N;
int n,m;
int h[N],e[M],w[M],ne[M],idx;
int dist[N];
int p[N];
int res[N];
int vis[N];
vector<PII>query[N];//first存查询的另外一个点,second存查询编号
void add(int a,int b,int c){
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
void dfs(int u,int fa){
    for(int i=h[u];i!=-1;i=ne[i]){
        int j=e[i];
        if(j==fa) continue;
        dist[j]=dist[u]+w[i];
        dfs(j,u);
    }
}
int find(int x){
    if(p[x]!=x) p[x]=find(p[x]);
    return p[x];
}
void tarjan(int u){
    vis[u]=1;//入u时,标记u
    for(int i=h[u];i!=-1;i=ne[i]){
        int j=e[i];
        if(!vis[j]){
            tarjan(j);
            p[j]=u;//回u时,v指向u,回u表示u往下搜完其中的一条路回到u
        }
    }
    //离u时,枚举LCA
    //离u表示u的子树已经全部搜完了
    for(auto item:query[u]){
        int y=item.first,id=item.second;
        if(vis[y]){
            int anc=find(y);
            res[id]=dist[u]+dist[y]-dist[anc]*2;
        }
    }
}
int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof h);
    for(int i=0;i<n-1;i++){
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c),add(b,a,c);
    }
    for(int i=0;i<m;i++){
        int a,b;
        cin>>a>>b;
        if(a!=b){
            query[a].push_back({b,i});
            query[b].push_back({a,i});
        }
    }
    for(int i=1;i<=n;i++) p[i]=i;
    dfs(1,-1);
    tarjan(1);
    for(int i=0;i<m;i++) cout<<res[i]<<endl;
    return 0;
}

 

 

[BJOI2018] 求和 - 洛谷

该题是点前缀和

AC代码:

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=3e5+10,mod=998244353;
int depth[N],fa[N][22];
int mi[60];//mi[j]表示depth[v]的j次幂
int s[N][60];//s[v][j]表示从根节点到v的路径节点的深度的j次幂之和
int n,m;
int root;
vector<vector<int>>e(N);
queue<int>q;
void bfs(int root){
    memset(depth,0x3f,sizeof depth);
    depth[0]=-1,depth[root]=0;
    q.push(root);
    while(q.size()){
        int t=q.front();
        q.pop();
        for(auto v:e[t]){
            if(depth[v]>depth[t]+1){
                depth[v]=depth[t]+1;
                q.push(v);
                fa[v][0]=t;
                for(int k=1;k<=20;k++) fa[v][k]=fa[fa[v][k-1]][k-1];
                for(int j=1;j<=50;j++) mi[j]=mi[j-1]*depth[v]%mod;
                for(int j=1;j<=50;j++) s[v][j]=(mi[j]+s[t][j])%mod;
            }
        }
    }
}
int lca(int a,int b){
    if(depth[a]<depth[b]) swap(a,b);
    for(int k=20;k>=0;k--){
        if(depth[fa[a][k]]>=depth[b]) a=fa[a][k];
    }
    if(a==b) return a;
    for(int k=20;k>=0;k--){
        if(fa[a][k]!=fa[b][k]){
            a=fa[a][k];
            b=fa[b][k];
        }
    }
    return fa[a][0];
}
void solve() {
    cin>>n;
    for(int i=0;i<n-1;i++){
        int a,b;
        cin>>a>>b;
        e[a].push_back(b);
        e[b].push_back(a);
    }
    root=1;
    mi[0]=1;
    bfs(root);
    cin>>m;
    for(int i=0;i<m;i++){
        int a,b,k;
        cin>>a>>b>>k;
        int l=lca(a,b);
        cout<<(s[a][k]+s[b][k]-s[l][k]-s[fa[l][0]][k]+2*mod)%mod<<endl;
    }
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t=1;
//    cin>>t;
    while(t--) {
        solve();
    }
    return 0;
}

 树上差分

 

 

[USACO15DEC] Max Flow P - 洛谷

AC代码:

#include<bits/stdc++.h>
#define endl '\n'
//#define int long long
using namespace std;
const int N=5e4+10;
int depth[N],fa[N][22];
int diff[N];//差分数组
int n,k;
int root;
int ans;
vector<vector<int>>e(N);
queue<int>q;
void bfs(int root){
    memset(depth,0x3f,sizeof depth);
    depth[0]=-1,depth[root]=0;
    q.push(root);
    while(q.size()){
        int t=q.front();
        q.pop();
        for(auto v:e[t]){
            if(depth[v]>depth[t]+1){
                depth[v]=depth[t]+1;
                q.push(v);
                fa[v][0]=t;
                for(int k=1;k<=20;k++) fa[v][k]=fa[fa[v][k-1]][k-1];
            }
        }
    }
}
void dfs(int u,int fa){
    for(auto v:e[u]){
        if(v==fa) continue;
        dfs(v,u);
        diff[u]+=diff[v];
    }
    ans=max(ans,diff[u]);
}
int lca(int a,int b){
    if(depth[a]<depth[b]) swap(a,b);
    for(int k=20;k>=0;k--){
        if(depth[fa[a][k]]>=depth[b]) a=fa[a][k];
    }
    if(a==b) return a;
    for(int k=20;k>=0;k--){
        if(fa[a][k]!=fa[b][k]){
            a=fa[a][k];
            b=fa[b][k];
        }
    }
    return fa[a][0];
}
void solve() {
    cin>>n>>k;
    for(int i=0;i<n-1;i++){
        int a,b;
        cin>>a>>b;
        e[a].push_back(b);
        e[b].push_back(a);
    }
    root=1;
    bfs(root);
    while(k--){
        int a,b;
        cin>>a>>b;
        int l=lca(a,b);
        diff[a]++,diff[b]++;
        diff[l]--,diff[fa[l][0]]--;
    }
    dfs(1,0);
    cout<<ans<<endl;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int t=1;
//    cin>>t;
    while(t--) {
        solve();
    }
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1061697.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

6轮面试阿里Android开发offer,薪资却从21k降到17k,在逗我?

一小伙工作快3年了&#xff0c;拿到了阿里云Android开发岗位P6的offer&#xff0c;算HR面一起&#xff0c;加起来有6轮面试了&#xff0c;将近3个月的时间&#xff0c;1轮同级 1轮Android用人部门leader 1轮Android 组leader 1轮项目CTO 1轮HR 1轮HRBP。 一路上各种事件分…

图示矩阵分解

特征值与特征向量 设 A A A 是 n 阶矩阵&#xff0c;如果存在数 λ \lambda λ 和 n 维非零列向量 x x x&#xff0c;满足关系式&#xff1a; A x λ x ( 1 ) Ax \lambda x\quad\quad(1) Axλx(1) 则数 λ \lambda λ 称为矩阵 A A A 的特征值&#xff0c;非零向量 x…

基于阴阳对优化的BP神经网络(分类应用) - 附代码

基于阴阳对优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于阴阳对优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.阴阳对优化BP神经网络3.1 BP神经网络参数设置3.2 阴阳对算法应用 4.测试结果&#x…

学习记忆——宫殿篇——记忆宫殿——数字编码——扑克牌记忆

扑克牌我们可以通过以下3点进行识记&#xff1a; 1、先把扑克牌进行编码转换 2、确定要进行记忆的记忆宫殿 3、把扑克牌与记忆宫殿一一对应 首先54张扑克牌除去大小王后剩下52张&#xff0c;因为世界赛不需要记忆大小王。52张扑克牌都有对应的编码&#xff0c;每2张扑克牌对应…

阿里云新账户、老账号、产品首购和同人账号什么意思?

阿里云账号分为云新账户、老账户、同人账号和同一用户有什么区别&#xff1f;阿里云官方推出的活动很多是限制账号类型的&#xff0c;常见的如阿里云新用户&#xff0c;什么是阿里云新用户&#xff1f;是指从未在阿里云官网购买过云产品的账号。下面阿小云来详细说下什么是阿里…

docker系列6:docker安装redis

传送门 docker系列1&#xff1a;docker安装 docker系列2&#xff1a;阿里云镜像加速器 docker系列3&#xff1a;docker镜像基本命令 docker系列4&#xff1a;docker容器基本命令 docker系列5&#xff1a;docker安装nginx Docker安装redis 通过前面4节&#xff0c;对docke…

JS中创建对象有几种方法

除了使用Object构造函数或者字面量都可以创建对象&#xff0c;但是也有缺点就是使用同一个接口创建很多对象&#xff0c;会产生大量的重复代码。 1. 工厂模式 简单来说就是把Object创建对象使用函数进行封装&#xff0c;然后再返回创建的对象&#xff0c;就可以创建多个相同对…

使用hugo+github搭建免费个人博客

使用hugogithub搭建免费个人博客 前提条件 win11电脑一台电脑安装了git电脑安装了hugogithub账号一个 个人博客本地搭建 初始化一个博客 打开cmd窗口&#xff0c;使用hugo新建一个博客工程 hugo new site blogtest下载主题 主题官网&#xff1a;themes.gohugo.io 在上面…

快手直播显示请求过快

快手直播显示请求过快 问题描述情况一问题描述原因分析解决方案:情况二问题描述解决方法问题描述 在使用快手直播网页版时,如果我们的请求过于频繁,系统可能无法及时显示所需内容。这种情况下,我们会收到一个稍后重试的提示。一般有两种情况。一种是直接返回一段json,里面…

园林园艺服务经营小程序商城的作用是什么

园林园艺属于高单价服务&#xff0c;同时还有各种衍生服务&#xff0c;对企业来说&#xff0c;多数情况下都是线下生意拓展及合作等&#xff0c;但其实线上也有一定深度&#xff0c;如服务售卖或园艺产品售卖等。 基于线上发展可以增强获客引流、品牌传播、产品销售经营、会员…

云原生微服务 第六章 Spring Cloud中使用OpenFeign

系列文章目录 第一章 Java线程池技术应用 第二章 CountDownLatch和Semaphone的应用 第三章 Spring Cloud 简介 第四章 Spring Cloud Netflix 之 Eureka 第五章 Spring Cloud Netflix 之 Ribbon 第六章 Spring Cloud 之 OpenFeign 文章目录 系列文章目录前言1、OpenFeign的实现…

mybatis-plus控制台打印sql(mybatis-Log)

配置了mybatis-plus.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImpl&#xff1b;但是mybatis执行的sql没有输出 需要检查点&#xff1a; 1、日志级别设置&#xff1a;请确保你的日志级别配置正确。如果日志级别设置得太低&#xff0c;可能导致SQL语句不…

软件工程与计算总结(四)项目管理基础

目录 一.项目和项目管理 二.团队组织与管理 三.软件质量保障 四.软件配置管理 五.项目实践 一.项目和项目管理 1.软件开发远不是纯粹的编程&#xff0c;随着软件规模的增长&#xff0c;软件开发活动也变得越来越复杂~ 2.软件项目就是要将所有的软件开发活动组织起来&#…

云原生Kubernetes:K8S集群kubectl命令汇总

目录 一、理论 1.概念 2. kubectl 帮助方法 3.kubectl 子命令使用分类 4.使用kubectl 命令的必要环境 5.kubectl 详细命令 一、理论 1.概念 kubectl是一个命令行工具&#xff0c;通过跟 K8S 集群的 API Server 通信&#xff0c;来执行集群的管理工作。 kubectl命令是操…

嵌入式Linux裸机开发(一)基础介绍及汇编LED驱动

系列文章目录 文章目录 系列文章目录前言IMX6ULL介绍主要资料IO表现形式 汇编LED驱动原理图初始化流程时钟设置IO复用设置电气属性设置使用GPIO 编写驱动编译程序编译.o文件地址链接.elf格式转换.bin反汇编&#xff08;其他&#xff09; 综合成Makefile完成一步编译烧录程序imx…

分词.join 保存txt

要求 分词.join 保存txt 第1种方法 分词.join 保存txt input多行文本 /storage/emulated/0/数据中心/txt没有就新建为什么会想到这么做 1. 是因为有分词文件&#x1f4c4;要处理 2. 对各种词语和线索进行分类 3. 解释一下生活中不常见的现象&#xff0c;但是深刻的符合社会…

十月四日作业

1、服务器 头文件&#xff1a; #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTcpServer> //服务器头文件 #include <QTcpSocket> //客户端头文件 #include <QList> //链表容器 #include <…

基于混合蛙跳优化的BP神经网络(分类应用) - 附代码

基于混合蛙跳优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于混合蛙跳优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.混合蛙跳优化BP神经网络3.1 BP神经网络参数设置3.2 混合蛙跳算法应用 4.测试结果…

全屋灯具选购指南,如何选择合适的灯具。福州中宅装饰,福州装修

灯具装修指南 灯具就像我们家里的星星&#xff0c;在黑暗中带给我们明亮&#xff0c;可是灯具如果选择的不好&#xff0c;这个效果不仅体现不出来&#xff0c;还会让人觉得烦躁。 灯具到底该怎么选呢&#xff1f;装修灯具有哪些注意事项呢&#xff1f;给大家做了一个总结&#…

集群服务器

文章目录 项目名:实现集群服务器技术栈通过这项目你学到(或者复习到)实现功能编码环境json环境muduo库boost库MySql数据库登录mysql&#xff1a;查看mysql服务开启了没有&#xff1f;mysql的服务器及开发包库chat&#xff0c;表 allgroup friend groupuser offlinemessage user…