LCA(最近公共祖先)与树上差分

article/2025/8/5 0:30:20

LCA:

我们先看一道例题洛谷p3379

这道题就是LCA的模板题

LCA大抵有三种方法处理,我们这里只讲两种

分别是Tarjan和倍增法,也分别是离线和在线算法

我们这里先讲Tarjan

Tarjan:

一提到Tarjan这个名字,相信大家都能想到dfs

没错,这个方法就是利用dfs解决LCA的

TarjanTarjan算法的优点在于相对稳定,时间复杂度也比较居中,也很容易理解。

下面详细介绍一下TarjanTarjan算法的基本思路:

  1. 任选一个点为根节点,从根节点开始。

  2. 遍历该点u所有子节点v,并标记这些子节点v已被访问过。

  3. 若是v还有子节点,返回第2步,否则下一步。

  4. 合并v到u上。

  5. 寻找与当前点u有询问关系的点v。

  6. 若是v已经被访问过了,则可以确认u和v的最近公共祖先为v被合并到的父亲节点a。

为了合并两个点,我们可以使用并查集(也是Tarjan发明的)

相信大家都会

不会的可以看这篇文章

一篇讲解并查集的文章

CODE:
 

#include<bits/stdc++.h>
using namespace std;
int fa[(int)5e5+10],vis[(int)5e5+10],ans[(int)5e5+10],n,m,s,x,y,a,b;
vector<int> v[(int)5e5+10];
vector<pair<int,int>> ask[(int)5e5+10];
int find(int x){if(fa[x]==x) return x;return fa[x]=find(fa[x]);
}
void merge(int x,int y){if(find(x)!=find(y))fa[find(x)]=find(y);
}
void tarjan(int x){vis[x]=1;for(int i=0;i<v[x].size();i++){if(vis[v[x][i]])continue;tarjan(v[x][i]);merge(v[x][i],x);}for(int i=0;i<ask[x].size();i++)if(vis[ask[x][i].first]) ans[ask[x][i].second]=find(ask[x][i].first);
}int main(){ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);cin>>n>>m>>s;for(int i=1;i<=int(5e5+10);i++) fa[i]=i;for(int i=1;i<n;i++){cin>>x>>y;v[x].push_back(y);v[y].push_back(x);}for(int i=1;i<=m;i++){cin>>a>>b;if(a==b)ans[i]=a;else{ask[a].push_back({b,i});ask[b].push_back({a,i});} }tarjan(s);for(int i=1;i<=m;i++)cout<<ans[i]<<"\n";
}

好,这就是大名鼎鼎的Tarjan算法,时间复杂度O(n+question number)

比其他的LCA算法都要快,但是它是离线算法,有的时候就只能用倍增法了。

倍增法:

首先我们让深度更大的节点跳直至跟另一个节点深度一致,然后再一起跳,交汇的地方就是LCA。

问题1:怎么高效率地跳来跳去

答:预处理,就跟ST表一样,我们令parents[u][i], 是u结点的2^i祖先结点。

然后进行dp就行了。

问题2:怎么知道跳到哪里?

答:直接倒着枚举

CODE:

#include <bits/stdc++.h>
using namespace std;
const int MAX = 5e5 + 10;
int n, m, s, f[MAX][20], dep[MAX];
vector<int> t[MAX];
void dfs(int u) {for (int i = 1; (1 << i) <= dep[u]; i++)f[u][i] = f[f[u][i - 1]][i - 1];for (int v : t[u]) {if (v == f[u][0]) continue;dep[v] = dep[u] + 1;f[v][0] = u;dfs(v);}
}
int LCA(int u, int v) {if (dep[u] < dep[v]) swap(u, v);for (int i = 19; i >= 0; i--)if (dep[u] - (1 << i) >= dep[v])u = f[u][i];if (u == v) return u;for (int i = 19; i >= 0; i--)if (f[u][i] != f[v][i])u = f[u][i], v = f[v][i];return f[u][0];
}
int main() {ios::sync_with_stdio(false);cin.tie(0);cin >> n >> m >> s;for (int i = 1; i < n; i++) {int x, y;cin >> x >> y;t[x].push_back(y);t[y].push_back(x);}dep[s] = 1;dfs(s);while (m--) {int x, y;cin >> x >> y;cout << LCA(x, y) << "\n";}
}

树上差分:

非常的简单,树上差分,顾名思义,在树上进行差分(废话)

这里只是讲一下基础的应用 ——点差分和边差分。

洛谷p3128

这一题就是点差分的模板题。

我们定义d[i]为点i的权值,那么若x到y有路线,为了使这条路线上的点都统计,那么d[x]++,d[y]++,d[lca(x,y)]--,d[father[lca(x,y)]]--,点u的经过次数就是以u为根的子树权值之和

如果知道了这个,那么这道题轻松就可以AC了

CODE:

#include <bits/stdc++.h>
using namespace std;
const int MAX=5e5+10;
int n,m,f[MAX][20],dep[MAX],d[MAX],ans[MAX],maxn=-1;
vector<int> t[MAX];
void dfs(int u){for(int i=1;(1<<i)<=dep[u];i++)f[u][i]=f[f[u][i-1]][i-1];for(int v:t[u])if(v!=f[u][0])dep[v]=dep[u]+1,f[v][0]=u,dfs(v);
}
int LCA(int u,int v){if(dep[u]<dep[v])swap(u,v);for(int i=19;i>=0;i--)if(dep[u]-(1<<i)>=dep[v])u=f[u][i];if(u==v)return u;for(int i=19;i>=0;i--)if(f[u][i]!=f[v][i])u=f[u][i],v=f[v][i];return f[u][0];
}
void dfs_ans(int u){ans[u]=d[u];for(int v:t[u])if(v!=f[u][0])dfs_ans(v),ans[u]+=ans[v];maxn=max(maxn,ans[u]);
}
int main(){ios::sync_with_stdio(0);cin.tie(0);cin>>n>>m;for(int i=1,x,y;i<n;i++)cin>>x>>y,t[x].push_back(y),t[y].push_back(x);dep[1]=1;dfs(1);for(int i=1,s,t,mid;i<=m;i++)cin>>s>>t,mid=LCA(s,t),d[s]++,d[t]++,d[mid]--,f[mid][0]?d[f[mid][0]]--:0;dfs_ans(1);cout<<maxn;
}

那么边差分怎么处理呢?

处理边有一点复杂,不妨处理点。把所有的边全部都下移,把边的权值存到点上。

即若x到y有一条边,那么若dep[x]>dep[y],那么这条边的权值就存在x上,否则就存在y上。

d[i]为i到father[i]的边的权值。若x到y有路线,d[x]+=1,d[y]+=1,d[lca(x,u)]-=2,i到f[i]的边的被覆盖次数就为以i为根的子树的权值之和。

相信看了以上的讲解,你肯定会做这道题了——洛谷p6869

这道题要在模板的基础上加上一点点微小的微不足道的微乎其微的修改就行了

CODE:

#include<bits/stdc++.h>
using namespace std;
const int N=200003;
long long n, x[N], y[N], f[N][21], len[N], lg[N], lm, va1[N], va2[N], ans[N], an;
vector<int> ed[N], le[N];
void dfs(int w, int fa) {f[w][0] = fa;len[w] = len[fa] + 1;lm = max(lm, len[w]);le[len[w]].push_back(w);for (int i = 1; i <= lg[len[w]]; ++i)f[w][i] = f[f[w][i - 1]][i - 1];for (int i = 0; i < ed[w].size(); ++i)if (ed[w][i] != fa)dfs(ed[w][i], w);
}
int lca(int x, int y) {if (len[x] < len[y])swap(x, y);while (len[x] > len[y])x = f[x][lg[len[x] - len[y]] - 1];if (x == y)return x;for (int i = lg[len[x]] - 1; i >= 0; --i) if (f[x][i] != f[y][i])x = f[x][i], y = f[y][i];return f[x][0];
}
int main() {ios::sync_with_stdio(0);cin.tie(0);cin >> n;for (int i = 1; i < n; ++i) {cin >> x[i] >> y[i] >> va1[i] >> va2[i];ed[x[i]].push_back(y[i]);ed[y[i]].push_back(x[i]);}for (int i = 1; i <= n; ++i)lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);dfs(1, 0);for (int i = 1; i < n; ++i) {ans[i]++;ans[i + 1]++;ans[lca(i, i + 1)] -= 2;}for (int i = lm; i > 1; --i)for (int j = 0; j < le[i].size(); ++j)ans[f[le[i][j]][0]] += ans[le[i][j]];for (int i = 1; i < n; ++i) {long long as = len[x[i]] > len[y[i]] ? ans[x[i]] : ans[y[i]];an += (as * va1[i] < va2[i]) ? as * va1[i] : va2[i];}cout << an;
}

如果你做了这些题目还不满足的话,可以尝试做一下这几道题。

洛谷p3398

洛谷p3258

洛谷p1967

洛谷p1084

洛谷p2783

洛谷p2597

洛谷p2680

洛谷p1600

洛谷p1081


http://www.hkcw.cn/article/WABEBkDmHq.shtml

相关文章

PCIe—TS1/TS2 之Polling下的应用(一)

前文 训练序列有序集用于比特对齐、符号对齐以及交换物理层参数。2.5GT/s和5GT/s速率时,训练序列有序集不会加扰,只用8b/10b 编码。但到8GT/s及以上速率时,采用128b/130b编码,符号有可能加扰有可能不加扰,具体参阅SPEC物理层章节,后续可能会写。 训练序列(TS1或…

Spring AI调用Ollama+DeepSeek

文章目录 Spring AI集成DeepSeek申请api_keySpringBoot工程 Spring AI聊天模型概述ChatClient接口角色预设流式响应 ChatModel接口实现简单的对话提示词 函数调用函数调用实现 AI调用Ollama下载并安装 Ollama拉取 DeepSeek 模型代码测试 Spring AI Spring AI是一个AI工程领域的…

maven中的maven-antrun-plugin插件详解

1. 核心功能2. 典型使用场景3. 配置示例4. 关键配置项5. 优缺点分析6. 最佳实践7. 常见问题8. 使用案例1. 基本配置2. 常用 Ant 任务示例文件操作执行系统命令条件判断 3. 绑定到不同生命周期阶段4. 传递参数到 Ant 脚本5. 跳过任务执行6. 调试与日志7. 完整示例 总结 maven-an…

1Remote远程会话管理以及一键启动虚拟机

1Remote远程会话管理以及一键启动虚拟机 前言 vmware中安装的虚拟机命令行没有右键粘贴功能&#xff0c;想用ssh但又得启动虚拟机又得连接SSH&#xff0c;本文使用开源的1Remote以及windows脚本来实现一键启动虚拟机并连接SSH。 实现过程 下载1Remote 下载地址&#xff1a…

Linux基础 文件描述符,重定向及缓冲区理解

&#x1f3d9;️正文 1、文件描述符 在使用 C语言 相关文件操作函数时&#xff0c;可以经常看到 FILE 这种类型&#xff0c;不同的 FILE* 表示不同的文件&#xff0c;实际进行读写时&#xff0c;根据 FILE* 进行操作即可。 #include<iostream> #include <cstdio>…

Vue 核心技术与实战智慧商城项目Day08-10

1.项目演示 2. 项目收获 3. 创建项目 4. 调整初始化目录 5. vant 组件库 6. 其他 Vue 组件库 7. vant 全部导入 和 按需导入 全部导入&#xff1a; 按需导入&#xff1a; 8. 项目中的 vw 适配 记得执行yarn serve module.exports {plugins: {postcss-px-to-viewport: {// vw适…

MacroDroid安卓版:自动化操作,让生活更智能

在智能手机的日常使用中&#xff0c;我们常常会遇到一些重复性的任务&#xff0c;如定时开启或关闭Wi-Fi、自动回复消息、根据位置调整音量等。这些任务虽然简单&#xff0c;但频繁操作会让人感到繁琐。MacroDroid安卓版正是为了解决这些问题而设计的&#xff0c;它是一款功能强…

基于springboot的益智游戏系统的设计与实现

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…

【深度学习】18. 生成模型:Variational Auto-Encoder(VAE)详解

Variational Auto-Encoder&#xff08;VAE&#xff09;详解 本节内容完整介绍 VAE 的模型结构、优化目标、重参数化技巧及其生成机制。 回顾&#xff1a;Autoencoder&#xff08;自编码器&#xff09; Autoencoder 是一种无监督学习模型&#xff0c;旨在从未标注的数据中学习压…

电容的深入探讨

文章目录 6.1.1 概念6.1.2 容抗6.1.3 电容种类6.1.3.1 安规电容6.1.3.2 电解电容6.1.3.3 电容命名 6.1.4 电容作用6.1.4.1 降压6.1.4.2 滤波6.1.4.3 延时6.1.4.4 解耦合6.1.4.5 旁路 6.1.5 电容的充放电6.1.6 电容储能量化6.1.7 电容的特性理解 6.1.1 概念 无源元件。&#xf…

《P3959 [NOIP 2017 提高组] 宝藏》

题目背景 NOIP2017 D2T2 题目描述 参与考古挖掘的小明得到了一份藏宝图&#xff0c;藏宝图上标出了 n 个深埋在地下的宝藏屋&#xff0c; 也给出了这 n 个宝藏屋之间可供开发的 m 条道路和它们的长度。 小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是&#xff0c;每个宝藏屋…

59、干系人概述

干系人&#xff08;Stakeholders&#xff09;是指在项目、组织、活动或任何特定情境中&#xff0c;具有利益、影响力或受其影响的人、团体或组织。他们可以是内部的&#xff08;如项目团队成员、管理层&#xff09;&#xff0c;也可以是外部的&#xff08;如客户、供应商、政府…

计算机视觉---YOLOv5

YOLOv5理论讲解 一、YOLOv5 整体架构解析 YOLOv5 延续了 YOLO 系列的 单阶段目标检测框架&#xff0c;包含 主干网络&#xff08;Backbone&#xff09;、颈部网络&#xff08;Neck&#xff09; 和 检测头&#xff08;Head&#xff09;&#xff0c;但在结构设计上更注重 轻量化…

前端框架进化史

本内容是对 You’ll Never Manually Update the DOM Again // Here’s Why 内容的翻译与整理。 你再也不需要手工更新DOM, 以下是原因 现代 JavaScript 框架&#xff0c;如 React、Vue、Svelte、Solid、Quick&#xff0c;以及本周推出的其他 786 个框架&#xff0c;都试图做一些…

Redis笔记

Redis&#xff08;Remote Dictionary Server&#xff09;&#xff0c;开源、基于C语言、内存可持久化的NoSQL的键值对数据库。 命令&#xff1a;redis命令不区分大小写&#xff0c;set和SET效果相同 主键&#xff08;key&#xff09;&#xff1a;任意二进制序列&#xff08;字…

flask pyinstaller打包exe,出现module not found问题

最近大作业要做一个项目要打包成可执行程序,这里说一下这个module not found问题,并提供几种可能的方案,如果严格按照这些来走就能解决常见问题,剩下的神仙问题建议问问ai或者清缓存重试 首先说一下目录问题,这应该是包括我(打包app.py)在内的大多数人遇见该报错问题的原因,提…

基于SpringBoot+Redis实现RabbitMQ幂等性设计,解决MQ重复消费问题

一、实现方案 本实验方案参考「RabbitMQ消息可靠性深度解析&#xff5c;从零构建高可靠消息系统的实战指南」 1、业务层幂等处理&#xff1a; 每个消息携带一个全局唯一ID&#xff0c;在业务处理过程中&#xff0c;首先检查这个ID是否已经被处理过。例如&#xff0c;将已处理消…

性能优化 - 案例篇:数据一致性

文章目录 Pre引言1. 分布式缓存概念2. Redis 与 Memcached 区别概览3. Spring Boot 中使用 Redis3.1 引入依赖与常用客户端3.2 RedisTemplate 的基本用法3.3 Spring Cache 注解式缓存 4. 秒杀业务简介及挑战5. Lua 脚本实现原子库存扣减5.1 准备阶段&#xff1a;数据预加载5.2 …

【深度学习】 19. 生成模型:Diffusion Models

Diffusion Models Diffusion Models 简介 Diffusion 模型是一类通过逐步添加噪声并再逆向还原的方式进行图像生成的深度生成模型。其基本流程包括&#xff1a; 前向过程&#xff08;Forward Process&#xff09;&#xff1a;将真实图像逐步加噪&#xff0c;最终变为高斯噪声…

【速通RAG实战:进阶】22、RAG 技术前沿探索:GraphRAG 等 13 种技术详解与应用场景

一、RAG技术的演进脉络与前沿分类 (一)从基础RAG到前沿创新的技术跃迁 传统RAG(检索增强生成)通过“检索-生成”两阶段解决LLM的知识时效性和准确性问题,但在复杂推理、多模态融合、成本控制等场景面临瓶颈。前沿RAG技术围绕检索精度、推理深度、生成质量、系统效率四大…