[洛谷P3865] ST表

洛谷P3865

ST表——RMQ问题

RMQ问题:

RMQ问题就是指求区间最值的问题。
这里的解决方法采用ST表

思想:

首先先回顾一下线段树的想法,将一个数列不断二分,并记录当前管辖区间内的最值。线段树求最值
但线段树查询操作并不是O(1),因此面对大量查询操作会t
因为线段树会把大区间划分成很多个不会重合的小区间,所以会耗时

而如果我们能将这个大区间直接划分成两个区间,直接用max求得,不是就会快很多了吗。
e.g. f[1,5]记录1-5之间的最值,f[4,8]记录5-8最值
因此求[1,8]最值就直接max(f[1,5],f[4,8]);


而我们采用倍增的思想的话,可以直接将一个大区间化分成两个可能重合的两个小区间
求f[n,m],可以直接求两个区间的max,达到了O(1)的效果。
区间划分

什么是倍增的思想? 顾名思义,就是不断翻倍1–>2,2–>4,4–>8
ST表便是让我们能知道每个点之后的 1个长度的最值,2的长度的最值,4个长度的最值,n^2的长度的最值


完美的情况来说,刚好可以把一个大区间分成两个不重合的小区间
分成不重合区间
记: f数组存区间最值
蓝色为(1,n)的f数组
而绿色是为: 若求 下一层区间最值 所需的另一个区间

比如求(1,8),你已经求得(1,4)的最值,只需要知道个(5,8)的最值,再max两个区间便可求得

会发现在 求f(5) 长度为4的区间最值时,会把f(5,8)求出来
求f(9) 长度为8的时候,会把f(9,16)求出来
即一个区间(n,m)恰好可以分为(n,2^k) (2^k +1,m) (k∈Z) 的两个区间


为什么会这样呢?【白学打死x……】
因为倍增既是二分的反操作,因此可以恰好把一个区间分成两区间

当然不完美的情况,即有重合的情况【即无法将一个区间(n,m)恰好分成(n,2^k) (2^k +1,m)的情况
我们仍可以把这个区间,分成一个(n,k1)(k1< m, k1为n+2^k) 和(k2,m)(k2> n, k2为m-2^k +1)

有重合的情况
如上图,可以把(1,6)分为(1,4[2^2])和(3[6-2^2 +1],6)


因此我们建立一个表,存所有倍增划分出来的区间最值,这里便是离线预处理
回答就直接以玄学操作查表并回答
大概的操作便是这样

实现

建立ST表


注意到: 以上讲的都是正着倍增的操作,即1找(1,1)(1,2)(1,4)(1,8)…(1,2^k)的最值
然后2找(2,2)(2,3)(2,4)(2,6)…

但个人写的是倒着倍增的,即1只找(1,1)的最值
2为(2,2)(2,1);3为(3,3)(3,2)(3,1);4为(4,4)(4,3),(4,2),(4,0)的最值

以下都是按倒着倍增来讲的


我们定义个ST表,ST[N][log2N]
ST[i][j]表示第i个数开始, 前面长度为2^j的最值
e.g.ST[13][3]便是(6[13-2^3 +1],13)的最值。【这里+1因为是长度为8,所以6到13长度才为8
至于为什么第二位定位log2N,因为每次倍增的话2^n到N,所以只需定义到log2N

因此易知,所有ST[i][0]便是其本身(ST[i][0]为长度为1范围的最值所以就是本身啦
之后开始正式求ST表了


我们会发现,ST[i][j]=max(ST[i][j-1],ST[i- 2^(j-1)][j-1]) 【这里看不懂先别急后面解释【因为我也很讨厌什么说一大堆无关的然后「我们易得:kjaf89&*^834ho18ikG90)93」就把结论说出来了明明完全看不懂呀_(:зゝ∠)_……
e.g.ST[13][3]求(6,13)的最值,便可从(6,9)(10,13)这两个区间中求出,便刚好为ST[13][2](10[9- 2^2 +1],13)和ST[9 (13- 2^2) ][2](6,9)当中求得

因为ST[i][j]表示i开始前面2^j长度的最值
2^j=2^(j-1)+2^(j-1),即长度为2^j的区间恰好可以分为两个2^(j-1)的区间

这里描述可以卖一波萌嘛x……
便可以Dp的思想,将ST表求出来


代码如下:

void Init()
{
    for (int i=1; i<=n; i++) F[i][0]=Num[i];    //刚开始f[n][0],即长度为1的序列中最小的就是它本身。
    //从2开始用Dp求 
    for (int i=2; i<=n; i++)
    { 
        F[i][1]=Work(F[i][0],F[i-1][0]);    //这里特殊处理个[i][1],因为2^(1-1)=1,而2<<(1-1)=2, 
        for (int j=2; j<=Log[i]; j++)
/*
这里自己提前打了个Log表,从来存 这个数最大能存到前面 几个2^k长度 e.g.log2=1;log3=1;log4=2;log5=2;log6=2;log7=2;log8=3;log9=3
3只能存到(2,3),4只能存到(1,4)[长度为4<---2^log[4] ], 9只能存到(2,9)[长度为8<---2^log[9]
*/
        F[i][j]=Work(F[i][j-1],F[i- (1<<(j-1))][j-1]);  //Dp操作前面已解释
    }
}

这里出现了一个Log表,作用已在代码中解释了,那么如何求出这个Log表呢[以下均用log代替log2

void InitLog()  //初始化Log数组 
{
    Log[1]=0,Log[2]=1;
    for (int i=3; i<=n; i++) Log[i]=Log[i/2]+1;
}

其实就这么简单几句,因为 logx=logxlog2+1=log(x2)+1

查询ST表

我们已经将所有区间都分成了2^k,根据前面的思路,怎么刚好把这个(l,r)区间拆成(l,l+2^k-1)与(r-2^k,r)这样的两个区间呢?
正确的操作:
我永远喜欢Super SASS……
这里必定有且仅有一个k使分成的两个区间为两个可能重合的小区间
如果k小1或大1必定会成为下面这两种区间【这里自己想一下吧很好理解orz……
窒息的操作:
niconiconi~……


嗦了这么多,k到底怎么找呢?……
既要满足

l<=r2k<=l+2k1<=r

【可看上图理解……
会易得 k=Log[r-l+1]
e.g. (2,7),k=Log[6]=2; 验证: (2,5)(4,8)刚好满足

因为r-l+1 代表是这个区间的长度,∴区间一半长度<=2^k<=区间长度,
然后balabala带到公式里会发现这样才刚好成立【其实是你懒得证吧x……【提示了这么多自己应该会证了吧233……

求出k过后,再看上图,应该不难理解直接return max(F[l+2^k-1][k],F[r][k])了吧……
【l+2^k -1的-1不懂的话自己啃啃吧qwq懒癌又犯了……


代码如下:

int Find(int l, int r)
{
    int k=Log[r-l+1];   //当这里查询的时候,我们将这个区间化为两个存在于我们ST表的区间,而这里即看(l,l+2^k)∪(l+2^k+1,r)=(l,r),发现k求解=log2(r-l+1) 
    return Work(F[l+(1<<k)-1][k],F[r][k]);
}

最后Ac代码:

#include<bits/stdc++.h>
#define N 100005
#define Work max
int Log[N],Num[N],F[N][30],n;
using namespace std;
inline int Read()
{
    int f=1,num=0;
    char t=getchar();
    while (t<'0' || t>'9') if (t=='-') f=-1,t=getchar(); else t=getchar();
    while (t>='0' && t<='9') num=num*10+t-'0',t=getchar();
    return f*num;
}
void InitLog()  //初始化Log数组 
{
    Log[1]=0,Log[2]=1;
    for (int i=3; i<=n; i++) Log[i]=Log[i/2]+1;
}
void Init()
{
    InitLog();
    for (int i=1; i<=n; i++) F[i][0]=Num[i];    //刚开始f[n][0],即长度为1的序列中最小的就是它本身。
    //2开始,log2=1;log3=1;log4=2;log5=2;log6=2;log7=2;log8=3; 
    for (int i=2; i<=n; i++)
    { 
        F[i][1]=Work(F[i][0],F[i-1][0]);//这里特殊处理个1,因为2^(1-1)=1,而2<<(1-1)=2, 
        for (int j=2; j<=Log[i]; j++) 
        F[i][j]=Work(F[i][j-1],F[i- (1<<(j-1))][j-1]);
    }
}
int Find(int l, int r)
{
    //if (l==r) return Num[l]; //这里l==r的时候直接特判【实际上是因为会求解出来k=0造成1<<k=1,即2^k=0出错…… //后面发现完全不用自己当时阿库娅了……//虽然也不知道为啥但是debug看出来的确不用orz……
    int k=Log[r-l+1];   //当这里查询的时候,我们将这个区间化为两个存在于我们ST表的区间,而这里即看(l,l+2^k)∪(l+2^k+1,r)=(l,r),发现k求解=log2(r-l+1) 
    return Work(F[l+(1<<k)-1][k],F[r][k]);
}
void Test() //输出测试数据而已懒得删_(:зゝ∠)_……
{
    for (int i=1; i<=n; i++)
    {
        cout<<"N is: "<<i<<endl; 
        for (int j=0; j<=Log[i]; j++) cout<<F[i][j]<<" ";
        cout<<endl;
    }
}
int main()
{
    int q,l,r;
    n=Read(),q=Read();
    for (int i=1; i<=n; i++) Num[i]=Read();
    Init();
    //Test();
    while (q--)
    {
        l=Read(),r=Read();
        printf("%d\n",Find(l,r));
    }
}

最后祝您,身体健康题题Ac,再见……

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值