经过了树状数组的折磨学习,小蒟蒻(hqk)又学了一个新的结构——(ST)表(其实应该是(ST)算法,因为(T)本来就有(table)的意思,但是由于大家都这么叫,接下来的文章里也沿用这一说法。
(ST)表
((1))区间(RMQ)问题
区间(RMQ)($ Rangespace Maximum/minimumspace Query ()问题,顾名思义,就是询问某个区间的最大最小值。这种问题通常有很多种解法,比如线段树、树状数组,还有像笛卡尔树、莫队这样的神仙做法。但是我们今天要探讨的解法是一种比较好理解的算法——)ST$表
((2))啥是(ST)表
(ST)表是基于动态规划的一种算法。为啥叫表呢?是因为(ST)表在运行过程中是先进行预处理,然后进行查询。这种算法能够实现(O(nlogn))预处理,(O(1))查询。是一种比较高效的算法,但是需要注意的一点是,这种算法的空间复杂度较高,需要一些优化(或者使用其他的算法来代替)才能通过一些毒瘤题目
((3))(ST)表的基本思想
其实,(ST)表的基本思想就是(dp)。
我们用(a[1···n])表示一组数。设(f[i][j])表示从(a[i])加到(a[i+2^i-1])这个范围内的最大值,也就是说(f[i][j])表示以(a[i])为起点连续(2^i)个数的最大值。由于元素个数为(2^j)个,所以我们可以考虑分治的思想,分而治之,分别求出左半边((2^{j-1}))的最大值,再求右半边的最大值,即(f[i][j]=max(f[i][j-1],f[i+2^{j-1}][j-1]))从前往后扫描一下就可以预处理出来。
接下来我们要考虑如何进行查询
每提问一个区间([l,r]),一定会存在一个数(x),使得(2^xleq r-l+1)。只要求出了这个值,我们就可以用已经与处理完毕的(f[][])来进行回答了。
具体方法是:(min(f[l][x],f[r-2^x+1][x]))这个东西可以再(O(1))的时间内求出来
等等,怎么求(x)呢?**
其实,求(x)的方法也很简单,就是(log_2^{r-l+1}),具体是为什么需要读者自己去思考,这里就不再赘述了
但是怎么求(log_2^{r-l+1})呢?
我们可以维护一个(log[]),其中(log[i])表示(log_2^i)。至于(log[i])的计算,我们可以用下面一个递推式:(log[i]=log[i/2]+1);不过如果再懒一点的话可以调用(cmath)库里的(log2)函数
((4))(ST)表的例题
其实就是(ST)表的实现
例题((1))
题目背景
这是一道(ST)表经典题——静态区间最大值
请注意最大数据时限只有(0.8s),数据强度不低,请务必保证你的每次查询复杂度为$ O(1)$
题目描述
给定一个长度为$ N (的数列,和) M $次询问,求出每一次询问的区间内数字的最大值。
输入输出格式
输入格式:
第一行包含两个整数$ N, M$,分别表示数列的长度和询问的个数。
第二行包含(N)个整数(记为 (a_i)),依次表示数列的第$ i$项。
接下来$ M$行,每行包含两个整数 (l_i, r_i),表示查询的区间为$ l_i, r_i$
输出格式:
输出包含$ M$行,每行一个整数,依次表示每一次询问的结果。
题解
这就是一道板子题啊!莫慌莫慌,都在代码里了——
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e6+5,logn=20;
int log[N],f[N][logn+5],a[N];
int n,m,x,y;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
log[0]=-1;//一定要注意这个小细节,才能使log[1]=0
for(int i=1;i<=n;++i)
{
f[i][0]=a[i];//将形如[i,i]的都标作a[i],作为dp的边界条件
log[i]=log[i>>1]+1;//对log的处理
}
for(int j=1;j<=logn;++j)//外循环是1~logn
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--)//循环读入数据
{
scanf("%d%d",&x,&y);
int s=log[y-x+1];
printf("%d
",max(f[x][s],f[y-(1<<s)+1][s]));//这些式子我们都进行过说明,这里就不说了
}
return 0;
}
例题((2))
题目背景
无
题目描述
为了检测生产流水线上总共(N)件产品的质量,我们首先给每一件产品打一个分数(A_i)表示其品质,然后统计前M件产品中质量最差的产品的分值(Q_m = min(A_1, A_2, ... A_m)),以及第(2)至第(M + 1)件的(Q_{m + 1}, Q_{m + 2} ...) 最后统计第(N - M + 1)至第(N)件的(Q_n)。根据(Q)再做进一步评估。
请你尽快求出(Q)序列。
输入输出格式
输入格式:
输入共两行。
第一行共两个数(N、M),由空格隔开。含义如前述。
第二行共(N)个数,表示(N)件产品的质量。
输出格式:
输出共(N - M + 1)行。
第(1)至(N - M + 1)行每行一个数,第(i)行的数(Q_{i + M - 1})。含义如前述。
题解
其实这道题也算一道模板题,只不过是将询问变成了让你自己循环跑一边,不过要注意的是,一定要将(m-1)后再进行循环
代码实现:
#include<cstdio>
#include<iostream>
using namespace std;
const int maxn=2000010;
const int logn=20;
int log[maxn],f[maxn][logn],a[maxn];
int n,m;
void init()
{
scanf("%d%d",&n,&m);
log[0]=-1;
for(int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
f[i][0]=a[i];
log[i]=log[i>>1]+1;
}
for(int j=1;j<=logn;++j)
for(int i=1;i+(1<<j)-1<=n;++i)
f[i][j]=min(f[i][j-1],f[i+(1<<j-1)][j-1]);
}
void work()
{
m-=1;
for(int i=1;i+m<=n;++i)
{
int s=log[m+1];
printf("%d
",min(f[i][s],f[i+m-(1<<s)+1][s]));
}
}
int main()
{
init();
work();
return 0;
}