题外话
之前听说过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;
}