[洛谷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
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
洛谷p1238是一个题目,具体要求是给定一个迷宫,求从起点到终点的最短路径。这个问题可以使用链示迷宫,并使用广度优先搜索算法来求解最短路径。 以下是一个示例代码,演示了如何使用链和广度优先搜索算法来解决洛谷p1238题目中的迷宫问题: ```python from collections import deque # 定义迷宫的链节点类 class Node: def __init__(self, x, y, val): self.x = x self.y = y self.val = val self.next = None # 构建迷宫的链 def build_maze(maze): m = len(maze) n = len(maze[0]) head = Node(0, 0, maze[0][0]) curr = head for i in range(m): for j in range(n): if i == 0 and j == 0: continue node = Node(i, j, maze[i][j]) curr.next = node curr = node return head # 广度优先搜索算法求解最短路径 def bfs(maze): m = len(maze) n = len(maze[0]) visited = [[False] * n for _ in range(m)] queue = deque([(0, 0, 0)]) # (x, y, step) visited[0][0] = True while queue: x, y, step = queue.popleft() if x == m - 1 and y == n - 1: return step for dx, dy in [(1, 0), (-1, 0), (0, 1), (0, -1)]: nx, ny = x + dx, y + dy if 0 <= nx < m and 0 <= ny < n and not visited[nx][ny] and maze[nx][ny] == 0: queue.append((nx, ny, step + 1)) visited[nx][ny] = True return -1 # 示例迷宫 maze = [ [0, 1, 0, 0, 0], [0, 1, 0, 1, 0], [0, 0, 0, 0, 0], [0, 1, 1, 1, 0], [0, 0, 0, 1, 0] ] # 构建迷宫的链 maze_head = build_maze(maze) # 使用广度优先搜索算法求解最短路径 shortest_path = bfs(maze) print("最短路径长度为:", shortest_path) ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值