题外话
之前听说过ST表,但是没想到这么有用又这么简单。。
前置知识
- DP,动态规划
难度
普 及 / 提 高 − \color{orange} 普及/提高- 普及/提高−
题意
给你一个长度为 N N N 的序列 A N A_N AN和一个 M M M
让你输出 Q i = min { A i , A i + 1 ⋯ A i + M − 1 } Q_i=\min\{A_i,A_{i+1}\cdots A_{i+M-1}\} Qi=min{
Ai,Ai+1⋯Ai+M−1}
其中 i = 1 t o N − m + 1 i=1\ to\ N-m+1 i=1 to N−m+1
数据范围
M ≤ N ≤ 1 0 5 M\le N\le 10^5 M≤N≤105
A i ≤ 1 0 6 A_i\le 10^6 Ai≤106
思路
-
ST表能干什么?
- 给你一个静态序列,能 O ( 1 ) O(1) O(1) 算出其中任何一个连续子序列中的最值。
-
相比于其他的一些做法,ST表有一些优势,适用于静态的多查询的情况。
- 时间复杂度 :
- 预处理: O ( N log N ) O(N\log N) O(NlogN)
- 查询: O ( 1 ) O(1) O(1)
- 空间复杂度 O ( N log N ) O(N\log N) O(NlogN)
- 时间复杂度 :
-
缺点:离线算法,无法更新序列
-
做法:设 d p [ i ] [ j ] = min { A i , A i + 1 ⋯ , A i + 2 j − 1 } dp[i][j]=\min\{A_i,A_{i+1}\cdots,A_{i+2^j-1}\} dp[i][j]=min{ Ai,Ai+1⋯,Ai+2j−1}记作 min [ i , i + 2 j − 1 ] \min[i,i+2^j-1] min[i,i+2j−1]
- 初始化: d p [ i ] [ 0 ] = A i \color{red}dp[i][0]=A_i dp[i][0]=Ai
- 预处理: d p [ i ] [ j ] = min { d p [ i ] [ j − 1 ] , d p [ i + 2 j − 1 ] [ j − 1 ] } \color{red}dp[i][j]=\min\{dp[i][j-1],dp[i+2^{j-1}][j-1]\} dp[i][j]=min{
dp[i][j−1],dp[i+2j−1][j−1]}
- 证明: min [ i , i + 2 j − 1 ] = min { min [ i , i + 2 j − 1 − 1 ] , min [ i + 2 j − 1 , i + 2 j − 1 + 2 j − 1 − 1 ] } = min { min [ i , i + 2 j − 1 − 1 ] , min [ i + 2 j − 1 , i + 2 j − 1 ] } \begin{aligned}\min[i,{i+2^j-1}]&=\min\{\min[i,{i+2^{j-1}-1}],\min[{i+2^{j-1}},{i+2^{j-1}+2^{j-1}-1}]\}\\&=\min\{\min[i,{i+2^{j-1}-1}],\min[{i+2^{j-1}},{i+2^{j}-1}]\}\end{aligned} min[i,i+2j−1]=min{ min[i,i+2j−1−1],min[i+2j−1,i+2j−1+2j−1−1]}=min{ min[i,i+2j−1−1],min[i+2j−1,i+2j−1]}
- 后面两个区间的并就是前面的区间,显然成立。
- 查询 [ l , r ] [l,r] [l,r] 区间内的最小值 min [ l , r ] \min[l,r] min[l,r]:
- 首先求出 k = log 2 ( r − l + 1 ) \color{red}k=\log_2(r-l+1) k=log2(r−l+1)
- 然后答案为 min { d p [ l ] [ k ] , d p [ r − 2 k + 1 ] [ k ] } \color{red}\min\{dp[l][k],dp[r-2^k+1][k]\} min{
dp[l][k],dp[r−2k+1][k]}
- 证明:只要求出这两个范围覆盖全区间即可
- 即证明: [ l , l + 2 k − 1 ] ∩ [ r − 2 k + 1 , r − 1 ] = [ l , r ] [l,l+2^k-1]\cap[r-2^k+1,r-1]=[l,r] [l,l+2k−1]∩[r−2k+1,r−1]=[l,r]
- 即证明: l + 2 k − 1 ≥ r − 2 k + 1 l+2^k-1\ge r-2^k+1 l+2k−1≥r−2k+1恒成立。(因为边界已经成立)
- 即证明: 2 k + 1 ≥ r − l + 2 2^{k+1}\ge r-l+2 2k+1≥r−l+2,带入 k = log 2 ( r − l + 1 ) k=\log_2(r-l+1) k=log2(r−l+1)
- 即证明: 2 r − 2 l + 2 ≥ r − l + 2 2r-2l+2\ge r-l+2 2r−2l+2≥r−l+2
- 显然成立 Q.E.D.
核心代码
- 时间复杂度 :
- 预处理: O ( N log N ) O(N\log N) O(NlogN)
- 查询: O ( 1 ) O(1) O(1)
- 空间复杂度 O ( N log N ) O(N\log N) O(NlogN)
/*
_ __ __ _ _
| | \ \ / / | | (_)
| |__ _ _ \ V /__ _ _ __ | | ___ _
| '_ \| | | | \ // _` | '_ \| | / _ \ |
| |_) | |_| | | | (_| | | | | |___| __/ |
|_.__/ \__, | \_/\__,_|_| |_\_____/\___|_|
__/ |
|___/
*/
const int MAX = 1e5+50;
int aa[MAX];
int dp[MAX][100];
int n,m;
void init(){
for(int i = 1;i <= n;++i)dp[i][0] = aa[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(m);
return min(dp[l][k],dp[r-(1<<k)+1][k]);
}
int main(){
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;++i)scanf("%d",&aa[i]);
init();
for(int i = m;i <= n;++i){
printf("%d\n",query(i-m+1,i));
}
return 0;
}