当前位置: 首页 > news >正文

【最近公共祖先】ST表法

模板题目:https://www.luogu.com.cn/problem/P3379

1 解题思路

求最近公共祖先,我们可以先用一个表f存储每个节点再跳1到若干跳可以到达的节点,另外我们用一个dep数组存储节点所处的深度(根节点的深度为1,这个用dfs去实现)。当一个询问来临,求点u和点v的最近公共祖先(假设u的深度大于v的深度),我们先让u来到和v同一深度的位置。之后看一下,如果来到同一深度时,两节点在同一位置,说明最近公共祖先是v。否则,就同时让u和v一起调到离最近公共祖先还差一步的深度,之后加一步就到最近公共祖先了。

2 进一步优化

洛谷的题目中提到:对于100%的数据,1≤N,M≤5×10^5。如果表f存的是各点每一跳能到达的节点,那么这样需要的空间很大,而且还会很浪费空间,因此,我们可以考虑使用ST表优化。
在这里,借用董晓老师的例子来说
在这里插入图片描述
图中有9个节点,用二维数组f存储下标的信息,f[6][2]表示节点6跳两跳到达的节点。在这里插入图片描述
想象一下如果是一个9个节点的斜二叉树的话,那就会占用9×9的空间,并且会有很多的0出现,比较浪费空间。因此我们可以使用ST表来实现。下标如果用二维数组f来存数据,f[3][2]表示节点3跳2的2次方(即跳4跳)跳后到达的节点,因为跳4跳已经超出根节点了,所以用0存储。在这里插入图片描述
如果我们想知道节点3跳3跳到达的节点怎么求呢?我们不妨把3拆分成2+1(二进制拆分),先让3跳2跳到达节点5,在看节点5跳1跳,即看f[5][0]到达的节点是1。
使用ST表存储可以把表格的列数大大减少,如果用N个节点,那么最多需要1+log2(N)列。在填表的时候,我们遵循从上到下从左到右的方向去填,先填f[1][0]到f[N][0],再填f[1][1]到f[N][1],…,最后填f[1][log2(N)+1]到f[N][log2(N)+1]。

3 变量及数组说明

  1. dep数组:存储节点的深度,根节点深度为1。
  2. f数组:存储ST表信息。
  3. 边使用链式前向星存储:
    ① head 数组:存储节点的一条出边
    ② to 数组:存储边的终点信息
    ③ nt 数组:存储要遍历的下一条边编号(一般来说,当前边和下一条边的起点是一样的)
  4. lg2数组:主要用于对层树做二进制拆分
  5. maxd:存储树的最大深度

4 AC代码

参考洛谷题解的代码:

#include<bits/stdc++.h>
using namespace std;
const int N=500005,M=N;
int n,m,s,lg2[N],f[N][22],head[N],ct,dep[N],maxd=-1; 
struct edge{int to,nxt;
}a[N<<2];
void add(int u,int v){a[++ct].to=v;a[ct].nxt=head[u];head[u]=ct;
}
void dfs(int x,int fa){f[x][0]=fa;dep[x]=dep[fa]+1;if(dep[x]>maxd) maxd=dep[x];for(int i=head[x];i;i=a[i].nxt){int tod=a[i].to;if(tod!=fa) dfs(tod,x);}
}
int lca(int u,int v){if(dep[u]<dep[v]) swap(u,v);// 先让u和v处于同一深度,深度差做二进制拆分while(dep[u]>dep[v]){u=f[u][lg2[dep[u]-dep[v]]];}if(u==v) return v;// 处于同一深度后,也是用二进制拆分,去让u和v再走一步祖先就相同。for(int i=lg2[dep[u]-1];i>=0;i--){if(f[u][i]!=f[v][i]){u=f[u][i],v=f[v][i];}}return f[u][0];
}
int main(){scanf("%d%d%d",&n,&m,&s);for(int i=2;i<=n;i++) lg2[i]=lg2[i/2]+1; //深度不会超过n,所以记录到n就可以了for(int i=1;i<n;i++){int u,v;scanf("%d%d",&u,&v);//这里要双向加边,便于dfs搜索,u和v谁是祖先要根据根节点dfs后来确定。add(u,v); add(v,u);}dfs(s,0);// 填ST表,要注意方向。具体要多少列,可以根据dfs之后的最大深度来定。for(int j=1;j<=lg2[maxd];j++){for(int i=1;i<=n;i++) f[i][j]=f[f[i][j-1]][j-1];}for(int i=1;i<=m;i++){int u,v;scanf("%d%d",&u,&v);printf("%d\n",lca(u,v));}return 0;
}
http://www.dtcms.com/a/307311.html

相关文章:

  • 从渠道渗透到圈层渗透:开源链动2+1模式、AI智能名片与S2B2C商城小程序的协同创新路径研究
  • 联通元景万悟 开源,抢先体验!!!
  • 技术速递|GitHub Copilot for Eclipse 迈出重要一步
  • SpringAI:AI工程应用框架新选择
  • 转码刷 LeetCode 笔记[1]:3.无重复字符的最长子串(python)
  • 一对一交友小程序 / APP 系统架构分析
  • n8n为什么建议在数组的每个item中添加json键?
  • python的异步、并发开发
  • 聊下多线程查询数据库
  • YOLO---01目标检测基础
  • C++从入门到起飞之——智能指针!
  • day 40 打卡-装饰器
  • Vulnhub Thales靶机复现详解
  • 02 基于sklearn的机械学习-KNN算法、模型选择与调优(交叉验证、朴素贝叶斯算法、拉普拉斯平滑)、决策树(信息增益、基尼指数)、随机森林
  • 【JEECG】JVxeTable表格拖拽排序功能
  • C语言:逆序输出0到9的数组元素
  • LeetCode Hot 100 搜索旋转排序数组
  • 腾讯云市场排名
  • 借助 Wisdom SSH 的 AI 助手构建 Linux 开发环境
  • 2419.按位与最大的最长子数组
  • duiLib 自定义资源目录
  • 限流算法详解:固定窗口、滑动窗口、令牌桶与漏桶算法全面对比
  • P1036 [NOIP 2002 普及组] 选数
  • 结合C++红黑树与AI人工智能的应用
  • Linux 系统日志管理与时钟同步实用指南
  • TCP和UDP编程的主要区别
  • 当人生低谷无人帮助时,如何独自奏响人生乐章
  • Linux系统编程Day1-- Linux系统的概念,主要内容
  • 查看遥控器6通道(以及其他通道)的实际PWM值
  • 洛谷 P1601 A+B Problem(高精)普及-