洛谷题解P3865 【模板】ST表 暨 浅谈ST表

原题传送门

\(\text{Solution}\)

这是一道ST表经典题——静态区间最大值

本文以此题为例,来浅析 \(\text{ST}\)

\(1\).概述
\(\text{ST}\) 表是一个解决 \(\text{RMQ}\) 问题的很有力的工具
它的本质其实是动态规划,运用到的思想主要是倍增思想。
可以做到 \(O(nlogn)\) 预处理,\(O(1)\) 查询。

\(2\).解析
\((1)\).预处理
\(\text{DP}\) 相似,这里我们令 \(st[i][j]\) 表示从 \(i\) 开始 \(2^j\) 个数的最值
易得 \(st[i][0]\ =\ a[i]\) (即输入时可以直接输入 \(st[i][0]\)
状态的转移 : (以最大值为例,最小值同理)

\[st[i][j]\ =\ max(st[i][j-1],st[i+(1 < <(j-1))][j-1]) \]

这里,我们应用的是 \(2^j\ =\ 2^{j-1}+2^{j-1}\) 这个关键的转化条件。
即可以把从 \(i\) 开始遍历 \(2^j\) 个位置转化为先遍历 \(2^{j-1}\) 个位置,再从当前位置再次遍历 \(2^{j-1}\) 个位置
状态转移方程即来源于此,不过需注意,第二次遍历的起点非 \(i\),是 \(i+2^{j-1}\)

所以可以得到以下代码 :

for(int j=1;j<=21;j++){		//2^21 = 2097152 > 2*10^5
	for(int i=1;i+(1<<j)-1<=n;i++){	//注意边界处理的 "-1" 
		st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
	}
}

\((2)\).查询
首先,对于一个区间 \([l,r]\) ,我们要知道它的长度是 \(r-l+1\) 并非 \(r-l\) (计算时当前元素也被包含在内,所以应该 \(+1\)
其次,对于一个在这个区间 \([l,r]\) 内的位置 \(i\)\(st[i][j]\) 中的 \(j\),它最多遍历 \(log_2 r-l+1\) 个位置,所以可以先预处理出来,再从左端点和右端点分别开始查询,保证 \(O(1)\) 且每个元素都被查询到。
所以可以得到以下代码 :

for(int i=1;i<=m;i++){
	read(l);read(r);
	len=log2(r-l+1);	//预处理 
	printf("%d\n",max(st[l][len],st[r-(1<<len)+1][len]));	//注意+1 
}

\(\text{Code}\)

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
inline void read(int &x){
	int f=1;
	char ch=getchar();
	x=0;
	while(ch<'0'||ch>'9'){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=(x<<3)+(x<<1)+ch-'0';
		ch=getchar();
	}
	x*=f;
} 
int n,m;
int st[100010][21];
int l,r;
inline int max(int x,int y){return x>y?x:y;}
int main(){
	read(n);read(m);
	for(int i=1;i<=n;i++) read(st[i][0]);
	for(int j=1;j<=21;j++){
		for(int i=1;i+(1<<j)-1<=n;i++){
			st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
		}
	} 
	int len;
	for(int i=1;i<=m;i++){
		read(l);read(r);
		len=log2(r-l+1);
		printf("%d\n",max(st[l][len],st[r-(1<<len)+1][len]));
	}
	return 0;
} 
posted @ 2020-10-16 21:13  _pwl  阅读(170)  评论(0编辑  收藏  举报
1 2 3
4