倍增:ST与LCA

倍增算法之:ST表 与 RMQ

讲解:

倍增思想,就是每次在原基础上往前“跳” 2n
RMQ参考 https://blog.csdn.net/qq_31759205/article/details/75008659
RMQ 问题,即区间最值查询问题,通常的做法(我会的做法)有 暴力、线段树……
这里介绍一种比较高效的算法:ST表。
ST 表可以做到 O(nlogn) 的预处理和 O(1) 的查询,是一种在码量和复杂度上都非常优秀的方法。

预处理:

  • a 是要求区间最值的序列,fi,j 表示从第 i 个数起连续 2j 个数中的最大值。(DP状态)

for example:
a={3,2,4,5,6,8,1,2,9,7};
f1,0表示从第一个数开始,长度为 20=1 的最大值,其实就是这个数 3

  • 很容易看出 fi,0=ai。(DP的初始值)
  • 我们把 fi,j 平均分成 2 段(因为 ii+j1 之间一定有偶数个数),从 ii+2(j1)1 为一段,i+2(j1)i+2j1 为一段,每段长度都是 2(j1)。我们可以得出状态转移方程:fi,j=max{fi,j1,fi+2(j1),j1}

查询:

  • 假如我们要查询的区间为 ij,那么我们需要找到覆盖整个闭区间(左边界取 i,右边界取 j)的最小幂(可以重叠)

比如我们要查询 1,2,3,4,5 中的最大值,我们可以先查询 1,2,3,4 中的最大值,再查询 2,3,4,5 中的最大值,最后取 max

  • 因为这个区间长度为 ji+1,所以我们就可以取 k=log2ji+1 则有 RMQij=max{fi,k,fj2k+1,k}.

洛谷模板题传送门:ST 表 && RMQ 问题

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m;
int f[100100][100];
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')f=-1;ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=x*10+ch-48;ch=getchar();
}
return x*f;
}
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
{
f[i][0]=read();
}
for(int j=1;(1<<j)<=n;j++)
{
for(int i=1;i+(1<<j)-1<=n;i++)
{
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
}
while(m--)
{
int k=0;
int l,r;
l=read();r=read();
while((1<<(k+1))<=r-l+1)k++;
int ans=max(f[l][k],f[r-(1<<k)+1][k]);
printf("%d\n",ans);
}
return 0;
}

倍增求 LCA

讲解:

LCA,即为最近公共祖先。
我们可以记录一个深度 depthi 表示节点 i 的深度,每往上跳一次,深度就减 1
定义 fi,j 表示从 i 点向上跳 2j 步会跳到哪里。
前面也用到了一个倍增的常用方法:2j=2j1+2j1
则状态转移方程为

fi,j=ffi,j1,j1

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn=500100;
const int maxm=500100;
int n,m,root;
int depth[maxn];//表示第i个节点的深度
int en=0;
int fir[maxn];
int f[maxn][25];
struct edge{
int v,next;
}ed[maxm*2];
void add_edge(int u,int v)
{
en++;
ed[en].v=v;
ed[en].next=fir[u];
fir[u]=en;
}
void dfs(int now,int fa)//当前节点为now,fa是他的父亲节点
{
depth[now]=depth[fa]+1;
f[now][0]=fa;//从这个点向上跳2^0=1步,到达父亲
for(int i=1;i<=20;i++)
{
f[now][i]=f[f[now][i-1]][i-1];//转移方程
}
for(int i=fir[now];i;i=ed[i].next)//遍历所有出边
if(ed[i].v!=fa) dfs(ed[i].v,now);
}
int get_lca(int a,int b)
{
if(depth[a]<depth[b])swap(a,b);//把 a 变成深度更深的点
for(int i=20;i>=0;i--)//a 向上跳,直到与 b 深度一致.
{
if(depth[f[a][i]]>=depth[b])//没有跳过
a=f[a][i];
}
//求lca
if(a==b) return a;
for(int i=20;i>=0;i--)
{
if(f[a][i]!=f[b][i])//说明两个点同时向上跳了 2^i 步后仍然没有重合,没有跳到lca
a=f[a][i],b=f[b][i];
}
return f[a][0];//返回他们的父节点
}
int main()
{
cin>>n>>m>>root;
depth[root]=0;
for(int i=1;i<n;i++)
{
int p1,p2;
cin>>p1>>p2;
add_edge(p1,p2);
add_edge(p2,p1);
}
dfs(root,0);
for(int i=1;i<=m;i++)
{
int a,b;
cin>>a>>b;
int p=get_lca(a,b);
cout<<p<<"\n";
}
return 0;
}
posted @   lazy_ZJY  阅读(31)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示