提高篇(1):RMQ问题与ST表

RMQ是英文Range Minimum/Maximum Query的缩写,是询问某个区间内的最值,这里讲一种解法:ST算法

ST算法通常用在要多次(10^6级别)询问区间最值的问题中,相比于线段树,它实现更简单,效率更高,但不支持修改,且一般只能维护最值。

ST算法实际上是动规,原理如下:

预处理:

一组数a[1]..a[n],设f[i][j]表示从a[i]到a[i+2^j-1]这个范围中的最值,元素个数为2^j个。

可以分成2部分,即从a[i]至a[i+2^(j-1)-1]与a[i+2^(j-1)]至a[i+2^j-1],所以

f[i][j]也可以分成f[i][j-1]与f[i+2^(j-1)][j-1],整个区间的最大值一定是左右两部分最大值的较大值,

于是可得状态转移方程:f[i][j]=max(f[i][j-1],f[i+2^(j-1)][j-1]),边界条件为f[i][0]=a[i],这样即可在O(n log(n))的时间内预处理f数组。

询问:

若询问区间[l,r]的最大值,可以先求出最大的x,满足2^x<=r-l+1,那么区间[l,r]=[l,l+2^x-1]U[r-2^x+1,r],两个区间的元素个数都为2^x,

所以[l,r]中的最大值为max(f[l][x],f[r-2^x+1][x]),可以在O(1)内计算出来(对于m次询问,需要O(m)的时间复杂度)。这两个区间虽然有交集,但对最值没有影响,这就是ST算法只使用于区间最值的原因。

总结:

求区间[x,y]的最大值:

k=log2(y-x+1);

ans=max(f[x][k],f[y-2^k+1][k]);

技巧:

因为cmath库中的log2函数效率不高,所以为了提高速度,通常会使用O(N)的递推预处理出1~N这N种区间长度各自对应的k值。

具体地,设log[x]表示log2(x)向下取整,则log[x]=log[x/2]+1。这样总时间复杂度为log(n*log(n)+m+n)。

放一道例题:

平衡阵容(Balanced Lineup)

题目描述

每天,农夫 John 的N(1 <= N <= 50,000)头牛总是按同一序列排队. 有一天, John决定让一些牛们玩一场飞盘比赛. 他准备找一群在对列中为置连续的牛来进行比赛. 但是为了避免水平悬殊,牛的身高不应该相差太大. John 准备了Q (1 <= Q <= 180,000) 个可能的牛的选择和所有牛的身高 (1 <= 身高 <= 1,000,000). 他想知道每一组里面最高和最低的牛的身高差别. 注意: 在最大数据上, 输入和输出将占用大部分运行时间.

输入

6 3

1

7

3

4

2

5

1 5

4 6

2 2

输出

6

3

0

[参考程序]

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 #include<climits>
 5 #include<cmath>
 6 #include<algorithm>
 7 using namespace std;
 8
 9 const int N = 50005;
10 int FMAX[N][20], FMIN[N][20];
11
12 void RMQ(int n)
13 {
14     for(int j = 1; j != 20; ++j)
15     {
16         for(int i = 1; i <= n; ++i)
17         {
18             if(i + (1 << j) - 1 <= n)
19             {
20                 FMAX[i][j] = max(FMAX[i][j - 1], FMAX[i + (1 << (j - 1))][j - 1]);
21                 FMIN[i][j] = min(FMIN[i][j - 1], FMIN[i + (1 << (j - 1))][j - 1]);
22             }
23         }
24     }
25 }
26
27 int main()
28 {
29     int num, query;
30     int a, b;
31     while(scanf("%d %d", &num, &query) != EOF)
32     {
33         for(int i = 1; i <= num; ++i)
34         {
35             scanf("%d", &FMAX[i][0]);
36             FMIN[i][0] = FMAX[i][0];
37         }
38         RMQ(num);
39         while(query--)
40         {
41             scanf("%d%d", &a, &b);
42             int k = (int)(log(b - a + 1.0) / log(2.0));
43             int maxsum = max(FMAX[a][k], FMAX[b - (1 << k) + 1][k]);
44             int minsum = min(FMIN[a][k], FMIN[b - (1 << k) + 1][k]);
45             printf("%d\n", maxsum - minsum);
46         }
47     }
48     return 0;
49 }

参考书籍:《信息学奥赛一本通·提高篇》

原文地址:https://www.cnblogs.com/jyx-blog/p/9305404.html

时间: 2024-10-08 12:53:01

提高篇(1):RMQ问题与ST表的相关文章

51Nod.1766.树上最远点对(树的直径 RMQ 线段树/ST表)

题目链接 \(Description\) 给定一棵树.每次询问给定\(a\sim b,c\sim d\)两个下标区间,从这两个区间中各取一个点,使得这两个点距离最远.输出最远距离. \(n,q\leq10^5\). \(Solution\) 一个集合直径的两端点,在被划分为两个集合后一定是两个集合直径的四个端点中的两个. 即假设将\(S\)分为两个集合后,另外两个集合的直径的两端点分别为a,b和c,d,那么\(S\)集合的直径的两端点一定是a,b,c,d中的两个. 证明类似树的直径. 所以信息可

rmq问题:ST表

存板子.O(nlogn)预处理,O(1)查询.空间O(nlogn). int d[1000006][25]; int mn[1000006]; void rmq_init() { for(int i=1;i<=n;i++) d[i][0]=a[i]; for(int j=1;(1<<j)<=n;j++) for(int i=1;i+(1<<j)-1<=n;i++) d[i][j]=min(d[i][j-1],d[i+(1<<(j-1))][j-1]);

RMQ问题 - ST表的简单应用

2017-08-26 22:25:57 writer:pprp 题意很简单,给你一串数字,问你给定区间中最大值减去给定区间中的最小值是多少? 用ST表即可实现 一开始无脑套模板,找了最大值,找了最小值,分别用两个函数实现,实际上十分冗余 所以TLE了 之后改成一个函数中同时处理最大值和最小值,就可以了 AC代码如下: /* @theme:poj 3264 @writer:pprp @declare:ST表(sparse table)稀疏表,用动态规划的思想来解决RMQ问题: @date:2017

Codeforces 803G Periodic RMQ Problem ST表+动态开节点线段树

思路: (我也不知道这是不是正解) ST表预处理出来原数列的两点之间的min 再搞一个动态开节点线段树 节点记录ans 和标记 lazy=-1 当前节点的ans可用  lazy=0 没被覆盖过 else 区间覆盖 push_up的时候要注意好多细节,, 数组尽量往大开 //By SiriusRen #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const in

【倍增】RMQ的ST表算法

RMQ问题:给定一个长度为N的区间,M个询问,每次询问Li到Ri这段区间元素的最大值/最小值. RMQ的高级写法一般有两种,即为线段树和ST表. 本文主要讲解一下ST表的写法.(以区间最大值为例) ST表:一种利用dp求解区间最值的倍增算法. 定义:f[i][j]表示i到i+2^j-1这段区间的最大值. 预处理:f[i][0]=a[i].即i到i区间的最大值就是a[i]. 状态转移:将f[i][j]平均分成两段,一段为f[i][j-1],另一段为f[i+2^(j-1)][j-1]. 两段的长度均

RMQ ST表

//ST表 //计算RMQ 即区间最值 //思想:区间dp+倍增 //注:将代码内所有max改成min即可求最小值 #include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> using namespace std; int n,m,l,r,a[100001]; int Pow[3

RMQ与st表

模板题 P3865 [模板]ST表 代码 实质也是DP,利用倍增获取从i开始长度为\(2^0,2^1,2^2-2^j\)的区间内的最大值. 这样对于任意区间\([l,r]\)都有,令\(dis=r-1+1\)则有\(k_0=2^c,2^{k_0}≤dis≤2^{k_0+1}\)这样在区间\([l,l+k0-1],[r-k0+1,r]\)完全覆盖了这个区域(中间可能有重叠) #include <iostream> #include <cmath> #include <cstdi

二维st表,一种暴力但却快速的二维RMQ利器

先上例题:[HAOI2007]理想的正方形 大部分人都用单调队列,但我不会.首先我们可以暴力枚举所有的可能的正方形,每次我们需要查询RMQ,如果用朴素的方法总复杂度就会变成N^4,你不T谁T 那怎么办,总不可能写正解吧,我们可以用二维st表,预处理N^2logN,每次O(1)查询,N^2水过. 注意,强制类型转换要用大括号括起要转换的东西,例如 (int)(log(n)/log(2)); Code: #include<iostream> #include<cstdio> #incl

HDU 2586 How far away ? &lt;&lt; LCA转RMQ+ST表 求树上任两点最短距离裸题

此题还有LCA+tarjin离线查询做法,详见这里 关于ST表 解决RMQ问题,dp[i][j]表示从第i位开始长度为(1<<j)的区间的最值 维护的时候采用倍增思想,维护dp[i][j+1]=opt(dp[i][j],dp[i+(1<<j)][j]) 查询的时候,两端点为l,r,则长度len=r-l,寻找一个k,使(1<<k)大于等于len/2,这样就使得 [l,l+(1<<k)]和[r-(1<<k),r]覆盖整个区间 然后此时,就可以直接用s