st 表
前置知识——可重复贡献问题
可重复贡献问题是对于运算 $opt$,运算的性质满足 $x\;opt\;x = x$,则对应的区间询问就是一个可重复的贡献问题,例如:最大值满足 $max(x,x)=x$,最大公因数满足 $gcd(x,x)=x$,因此 rmq 和 gcd 就都是一个可重复贡献的问题。,但是例如区间和就不满足这个性质,因为在求解区间和的过程中采用的预处理区间会发生重叠,导致重叠部分被重复计算,因此对于 $opt$ 操作还需要满足这个性质才能够使用ST表进行求解。
st表在 rmq 中的应用:
不熟悉 rmq 的请先读这篇文章:RMQ。
ST 表基于 倍增 思想,可以做到 $O(n \log n)$ 预处理,$O(1)$ 回答每个询问,但是 不支持修改操作。
我们发现 ,也就是说,区间最大值是一个具有“可重复贡献”性质的问题。即使用来求解的预处理区间有重叠部分,只要这些区间的并集是所求的区间,最终计算出的答案就是正确的。
如果手动模拟一下,可以发现我们能使用至多两个预处理过的区间来覆盖询问区间,也就是说询问时的时间复杂度可以被降至 ,在处理有大量询问的题目时十分有效。
数组 $a$ 是指原区间,也就是要求最大值的区间。
数组 $f$ 是指 st 表。
令 $f[i][j]$ 表示区间 $[i,i+2^j-1]$ 的最大值。
显然 $f[i][0]=a[i]$。
根据定义式,第二维就相当于倍增的时候“跳了 $2^j$ 步”,依据倍增的思路,写出状态转移方程:$f[i][j]=max(f[i][j-1],f[i+2^{j-1}][j-1]$。
以上就是预处理部分。而对于查询,可以简单实现如下:
对于每个询问 $[l,r]$,我们令 $t=\lfloor log_2(r-l+1) \rfloor$,我们把它分成两部分:$[l,l+2^t-1]$ 和 $[r-2^t+1,r]$ 两部分的结果的最大值就是回答。
根据上面对于“可重复贡献问题”的论证,由于最大值是“可重复贡献问题”,重叠并不会对区间最大值产生影响。而我们分成的两个区间一定覆盖 $[l,r]$ 整个区间,所以可以保证答案的正确性。
核心代码:
const int N = 1e5+5;
int lg[N];
int st[N][20];
void build(int n) {
for(int i=2;i<=n;++i) lg[i]=lg[i>>1]+1;
for(int i=1;i<=n;++i) cin>>st[i][0];
for(int j=1;j<19;++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 query(int l,int r) {
int t=lg[r-l+1];
return max(st[l][t],st[r-(1<<t)+1][t]);
}
st 表练习题:https://www.luogu.com.cn/problem/P3865
代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
int st[N][20];
int lg[N];
int query(int l,int r) {
int t=lg[r-l+1];
return max(st[l][t],st[r-(1<<t)+1][t]);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int n,m;
cin>>n>>m;
for(int i=2;i<=n;++i) lg[i]=lg[i>>1]+1;
for(int i=1;i<=n;++i) cin>>st[i][0];
for(int j=1;j<19;++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]);
while(m--) {
int l,r;
cin>>l>>r;
int t=query(l,r);
cout<<t<<'\n';
}
return 0;
}

浙公网安备 33010602011771号