RMQ
区间最值问题
问区间最值问题, 没有修改操作
ST表:
ST表:支持查询任意区间[l,r]的最值,但是不支持修改
优势:
- 查询复杂度 O ( 1 ) O(1) O(1)
- 建表复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
- 代码量小
ST表的形式:预处理
ST表的本质:动态规划求区间最值
- 状态 d p i , j dp_{i,j} dpi,j以 i i i为起点长度为 2 j 2^j 2j的区间最值 min / max ( i , i + 2 j − 1 ) \min/\max(i,i+2^j-1) min/max(i,i+2j−1)
- 方程 d p i , j = m i n / m a x ( d p i , j − 1 , d p i + 2 j − 1 , j − 1 ) dp_{i,j} = min/max(dp_{i,j-1}, dp_{i+2^{j - 1},j-1}) dpi,j=min/max(dpi,j−1,dpi+2j−1,j−1)
- 初始化 d p i , 0 = a i dp_{i,0} = a_i dpi,0=ai
- 对于任意 区间如何查询
- 将
l
e
n
len
len转换成二进制,把
[
l
,
r
]
[l,r]
[l,r]这个区间按照二进制长度完整的拆开 时间复杂度
O
(
l
o
g
n
)
O(logn)
O(logn)
就不是 O ( 1 ) O(1) O(1)的了,与线段树和树状数组的优势少了一项 -
[
l
,
r
]
[l,r]
[l,r]长度为
l
e
n
=
r
−
l
+
1
len=r-l+1
len=r−l+1先求出最大的
2
k
≤
l
e
n
2^k \leq len
2k≤len
注意 最大值或最小值 重复计算是没有关系的 得式子: m i n / m a x ( d p l , k , d p r − 2 k + 1 , k ) min/max(dp_{l,k},dp_{r-2^k+1,k}) min/max(dpl,k,dpr−2k+1,k)
- 将
l
e
n
len
len转换成二进制,把
[
l
,
r
]
[l,r]
[l,r]这个区间按照二进制长度完整的拆开 时间复杂度
O
(
l
o
g
n
)
O(logn)
O(logn)
void init()
{
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]);
// dp[i][j] = max(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
}
}
}
int query(int l, int r)
{
int i = (int)(log2(r - l + 1));
return min(dp[l][i], dp[r - (1 << i) + 1][i]);
// return max(dp[l][i], dp[r - (1 << i) + 1][i]);
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
dp[i][0] = a[i];
}
init();
while (m--)
{
int l, r;
scanf("%d%d", &l, &r);
printf("%d\n", query(l, r));
}
return 0;
}
luogu习题
P3865
思路
ST表模板题
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int inf = 0x3f3f3f3f;
const int MOD = 1e9 + 7, N = 1e5 + 5;
int dp[N][25];
int n, m;
void initST()
{
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 = (int)(log2(r - l + 1));
return max(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", &dp[i][0]);
initST();
while (m--)
{
int l, r;
scanf("%d%d", &l, &r);
printf("%d\n", query(l, r));
}
return 0;
}
二维ST表
这里讨论的是正方形,而不是矩形,矩形在正方形的基础上加一维即可
- 状态 s t [ i ] [ j ] [ k ] st[i][j][k] st[i][j][k]表示从 ( i , j ) (i, j) (i,j)为起点的长度和高度为 2 k 2^k 2k的正方形的最大值
- 转移
设 x x x 为 2 k − 1 2^{k-1} 2k−1
s t [ i ] [ j ] [ k ] = max ( s t [ i ] [ j ] [ k − 1 ] , s t [ i + x ] [ j ] [ k − 1 ] , s t [ i ] [ j + x ] [ k − 1 ] , s t [ i + x ] [ j + x ] [ k − 1 ] ) st[i][j][k] = \max(st[i][j][k-1],st[i+x][j][k-1],st[i][j+x][k-1],st[i+x][j+x][k-1]) st[i][j][k]=max(st[i][j][k−1],st[i+x][j][k−1],st[i][j+x][k−1],st[i+x][j+x][k−1]) - 初始化 d p [ i ] [ j ] [ 0 ] = a [ i ] [ j ] dp[i][j][0]=a[i][j] dp[i][j][0]=a[i][j]
- 对于任意矩形如何查询
设 k k k 为 log 2 ( s z ) \log_2(sz) log2(sz)(长度 / / / 宽度) x x x 为 2 k 2^k 2k
max ( s t [ s x ] [ s y ] [ k ] , s t [ s x ] [ f y − x + 1 ] [ k ] , s t [ f x − x + 1 ] [ s y ] [ k ] , s t [ f x − x + 1 ] [ f y − x + 1 ] [ k ] ) ; \max({st[sx][sy][k], st[sx][fy-x+1][k], st[fx-x+1][sy][k], st[fx-x+1][fy-x+1][k]}); max(st[sx][sy][k],st[sx][fy−x+1][k],st[fx−x+1][sy][k],st[fx−x+1][fy−x+1][k]);
模板:
#include <bits/stdc++.h>
#define ll long long
#define PII pair<int, int>
using namespace std;
const int inf = 0x3f3f3f3f;
const int MOD = 1e9 + 7, N = 1005;
int A, B, n;
int lg[N];
int a[N][N];
int st[N][N][11];
void init()
{
for (int k = 1; k <= 10; ++k)
{
for (int i = 1; i + (1 << k) - 1 <= A; ++i)
{
for (int j = 1; j + (1 << k) - 1 <= B; ++j)
{
int x = 1 << (k - 1);
st[i][j][k] = max({st[i][j][k - 1], st[i + x][j][k - 1],
st[i][j + x][k - 1], st[i + x][j + x][k - 1]});
}
}
}
}
// 第一个数是最大值,第二个数是最小值 int query(int sx, int sy, int fx, int fy)
{
int k = lg[fx - sx + 1], x = (1 << k) - 1;
int mx = max({st[sx][sy][k], st[sx][fy - x][k], st[fx - x][sy][k], st[fx - x][fy - x][k]});
return mx;
}
int main()
{
scanf("%d%d%d", &A, &B, &n);
for (int i = 2; i <= 1000; ++i) lg[i] = lg[i / 2] + 1;
for (int i = 1; i <= A; ++i)
for (int j = 1; j <= B; ++j)
{
scanf("%d", &a[i][j]);
st[i][j][0] = a[i][j];
}
init();
// ......
return 0;
}
例题:P2216 [HAOI2007] 理想的正方形
代码:
#include <bits/stdc++.h>
#define ll long long
#define PII pair<int, int>
using namespace std;
const int inf = 0x3f3f3f3f;
const int MOD = 1e9 + 7, N = 1005;
int A, B, n;
int lg[N];
int a[N][N];
int stMax[N][N][11], stMin[N][N][11];
void init()
{
for (int k = 1; k <= 10; ++k)
{
for (int i = 1; i + (1 << k) - 1 <= A; ++i)
{
for (int j = 1; j + (1 << k) - 1 <= B; ++j)
{
int x = 1 << (k - 1);
stMax[i][j][k] = max({stMax[i][j][k - 1], stMax[i + x][j][k - 1],
stMax[i][j + x][k - 1], stMax[i + x][j + x][k - 1]});
stMin[i][j][k] = min({stMin[i][j][k - 1], stMin[i + x][j][k - 1],
stMin[i][j + x][k - 1], stMin[i + x][j + x][k - 1]});
}
}
}
}
// 第一个数是最大值,第二个数是最小值
PII query(int sx, int sy, int fx, int fy)
{
int k = lg[fx - sx + 1], x = (1 << k) - 1;
int mx = max({stMax[sx][sy][k], stMax[sx][fy - x][k], stMax[fx - x][sy][k], stMax[fx - x][fy - x][k]});
int mn = min({stMin[sx][sy][k], stMin[sx][fy - x][k], stMin[fx - x][sy][k], stMin[fx - x][fy - x][k]});
return {mx, mn};
}
int main()
{
scanf("%d%d%d", &A, &B, &n);
for (int i = 2; i <= 1000; ++i) lg[i] = lg[i / 2] + 1;
for (int i = 1; i <= A; ++i)
for (int j = 1; j <= B; ++j)
{
scanf("%d", &a[i][j]);
stMax[i][j][0] = stMin[i][j][0] = a[i][j];
}
init();
int ans = inf;
for (int i = 1; i <= A - n + 1; ++i)
{
for (int j = 1; j <= B - n + 1; ++j)
{
PII x = query(i, j, i + n - 1, j + n - 1);
ans = min(ans, x.first - x.second);
}
}
printf("%d", ans);
return 0;
}