ST表
查询时间:O(1)
建表时间:O(nlogn)
注意: ST不支持在线修改
采用思想:倍增思想
以 F [ i ] [ j ] F[i][j] F[i][j]代表区间 [ i , i + 2 j − 1 ] [i,i+2^j-1] [i,i+2j−1]的区间最大值为例:
F [ i ] [ j ] F[i][j] F[i][j] :代表区间 [ i , i + 2 j − 1 ] [i,i+2^j-1] [i,i+2j−1]的区间最大值,区间长度为 2 j 2^j 2j
递推求 F [ i ] [ j ] F[i][j] F[i][j]: 把区间长度为 2 j 2^j 2j的区间分成两个区间长度为 2 j − 1 2^{j-1} 2j−1的区间
则递推公式: F [ i ] [ j ] = m a x ( 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][j]=max(F[i][j−1],F[i+2j−1][j−1])
ST表创建:
n
: 代表a[]
数组长度
变量k
: 满足
2
k
≤
n
<
2
k
+
1
2^k \leq n \lt2^{k+1}
2k≤n<2k+1,
2
k
2^k
2k代表最大区间长度,k
需要尽可能的大,且要满足前面所说条件,故
k
=
l
o
g
2
n
k = log_{2}n
k=log2n(此时已经下取整了)
void init(int n)
{
for(int i=1;i<=n;i++)
f[i][0] = a[i];
int k = log2(n);
//循环方式类似 区间DP
//判断也可以 : (1<<j) <= n
for(int j=1;j<=k;j++)//循环k,区间长度从小到大
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]);
}
ST表查询:
查询 [ l , r ] [l,r] [l,r]区间最大值:
和上面一样,先计算k
值,
k
=
l
o
g
2
(
r
−
l
+
1
)
k = log_2({r-l+1})
k=log2(r−l+1)
区间 [ l , r ] [l,r] [l,r]最值 m x = m a x ( f [ l ] [ k ] , f [ r − ( 1 < < k ) + 1 ] [ k ] ) mx = max(f[l][k],f[r-(1<<k)+1][k]) mx=max(f[l][k],f[r−(1<<k)+1][k]),取两个区间的最值,虽然两个区间可能有重合,但是不影响结果。
int query(int l,int r)
{
int k = log2(r-l+1);
//取两个区间的最值
return max(f[l][k],f[r-(1<<k)+1][k]);
}
例题
题目大意:给定
n
个数,有m
个询问,对于每个询问,你需要回答区间 [ l , r ] [l,r] [l,r] 中的最大值。
考虑暴力做法。每次都对区间 [ l , r ] [l,r] [l,r] 扫描一遍,求出最大值。
显然,这个算法会超时。
这里采用ST表思想:
二维数组 d p [ i ] [ j ] dp[i][j] dp[i][j]表示从第i位开始连续 2 j 2^j 2j 个数中的最小值
求 d p [ i ] [ j ] dp[i][j] dp[i][j] 的时候可以把它分成两部分,第一部分是从 [ i , i + 2 j − 1 − 1 ] [i,i+2^{j-1}-1] [i,i+2j−1−1] ,第二部分从 [ i + 2 j − 1 , i + 2 j − 1 ] [i+2^{j-1},i+2^{j}-1] [i+2j−1,i+2j−1] ,为什么可以这么分呢?
其实我们都知道二进制数前一个数是后一个的两倍,那么可以把 [ i , i + 2 j − 1 ] [i , i+2^j-1] [i,i+2j−1] 这个区间分成相等长度的两个区间, 那么转移方程很容易就写出来了。( d p [ i ] [ 0 ] dp[i][0] dp[i][0]就表示第i个数字本身)
模板
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int a[maxn];
int dp[200005][22];
void rmq()
{
//初始化
for(int i=1;i<=N;i++)
dp[i][0]=a[i];
for(int j=1;(1<<j)<=N;j++)
for(int i=1;i+(1<<j)-1<=N;i++)
dp[i][j]=min(dp[i][j-1],dp[i+(1<<j-1)][j-1]);
}
//询问
int query(int l,int r)
{
int k=log2(r-l+1);
return min(dp[l][k],dp[r-(1<<k)+1][k]);
}
int main()
{
return 0;
}
例题:
牛客网:天才的记忆
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+5;
int a[maxn];
int dp[200005][22];
int N,m;
void rmq()
{
//初始化
for(int i=1;i<=N;i++)
dp[i][0]=a[i];
for(int j=1;(1<<j)<=N;j++)
for(int i=1;i+(1<<j)-1<=N;i++)
dp[i][j]=max(dp[i][j-1],dp[i+(1<<j-1)][j-1]);
}
int query(int l,int r)
{
int k=log2(r-l+1);
return max(dp[l][k],dp[r-(1<<k)+1][k]);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>N;
for(int i=1;i<=N;i++) cin>>a[i];
cin>>m;
rmq();
while(m--)
{
int l,r;
cin>>l>>r;
cout<<query(l,r)<<'\n';
}
return 0;
}