ST表学习笔记

经过了树状数组的折磨学习,小蒟蒻\(hqk\)又学了一个新的结构——\(ST\)表(其实应该是\(ST\)算法,因为\(T\)本来就有\(table\)的意思,但是由于大家都这么叫,接下来的文章里也沿用这一说法。

\(ST\)

\(1\))区间\(RMQ\)问题

区间\(RMQ\)($ Range\space Maximum/minimum\space 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^x\leq 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\)

P3865 【模板】ST表

题目背景

这是一道\(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\n",max(f[x][s],f[y-(1<<s)+1][s]));//这些式子我们都进行过说明,这里就不说了
	}
	return 0;
}

例题(\(2\)

P2251 质量检测

题目背景

题目描述

为了检测生产流水线上总共\(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\n",min(f[i][s],f[i+m-(1<<s)+1][s]));
	}
}

int main()
{
	init();
	work();
	return 0;
}
posted @ 2019-06-29 09:38  蒟蒻hqk  阅读(299)  评论(0编辑  收藏  举报