單調佇列可以 \(O(n)\) 求出子區間的最值,且順序上要求區間的左端點和右端點單調不降。
引入
P1886 滑動視窗 /【模板】單調佇列
定義一個佇列 \(q\),我們讓 \(q\) 中的元素滿足單調不降。因此當 \(x\) 入隊時,從前往後讓不在當前範圍的元素出隊,從後往前將 \(< x\) 的元素全部出隊,然後加入 \(x\)。那麼答案就是佇列第一個元素。時間複雜度為 \(O(n)\)。
#include <bits/stdc++.h>
constexpr int N = 1e6 + 5;
int n, m, a[N];
int q[N], head, tail;
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> n >> m;
for (int i = 1; i <= n; i++) {
std::cin >> a[i];
}
head = 0, tail = -1;
for (int i = 1; i <= n; i++) {
while (head <= tail && q[head] < i - m + 1) {
head++;
}
while (head <= tail && a[q[tail]] > a[i]) {
tail--;
}
q[++tail] = i;
if (i >= m) {
std::cout << a[q[head]] << ' ';
}
}
std::cout << '\n';
head = 0, tail = -1;
for (int i = 1; i <= n; i++) {
while (head <= tail && q[head] < i - m + 1) {
head++;
}
while (head <= tail && a[q[tail]] < a[i]) {
tail--;
}
q[++tail] = i;
if (i >= m) {
std::cout << a[q[head]] << ' ';
}
}
std::cout << '\n';
return 0;
}
例題
AcWing 298. 圍欄
按照 \(S\) 進行排序,設 \(f(i, j)\) 表示當前為第 \(i\) 個工匠,粉刷了 \(j\) 塊木板(可以存在未刷的木板),則:
- 不刷任何木板:\(f(i - 1, j)\)。
- 不刷當前木板:\(f(i, j - 1)\)。
- 刷連續一段木板,設粉刷 \([k + 1, j]\) 的木板:
\[f(i, j) = \max\limits_{j - L_i \leq k < S_i}\left\{ f(i - 1, k) + (j - k) \times P_i \right\}, j \geq S_i
\]
時間複雜度為 \(O(n^2 m)\)。發現第三種情況我們需要 \(O(n)\) 轉移,考慮最佳化。
將式子變形,可以得到:
\[f(i, j) = \max\limits_{j - L_i \leq k < S_i}\left\{ f(i - 1, k) - k \times P_i \right\} + j \times P_i, j \geq S_i
\]
可以用單調佇列維護 \(f(i - 1, k) - k \times P_i\)。時間複雜度為 \(O(nm)\)。
#include <bits/stdc++.h>
constexpr int N = 105, M = 16005;
int n, m, f[N][M];
int q[M], head, tail;
struct Node {
int L, P, S;
bool operator<(const Node &rhs) {
return S < rhs.S;
}
} a[N];
int calc(int i, int j) {
return f[i - 1][j] - a[i].P * j;
}
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cin >> n >> m;
for (int i = 1; i <= m; i++) {
int x, y, z;
std::cin >> x >> y >> z;
a[i] = {x, y, z};
}
std::sort(a + 1, a + 1 + m);
for (int i = 1; i <= m; i++) {
head = 0, tail = -1;
q[++tail] = 0;
for (int j = 1; j <= n; j++) {
f[i][j] = std::max(f[i - 1][j], f[i][j - 1]);
while (head <= tail && q[head] < j - a[i].L) {
head++;
}
if (j < a[i].S) {
while (head <= tail && calc(i, q[tail]) < calc(i, j)) {
tail--;
}
q[++tail] = j;
continue;
}
if (head <= tail) {
f[i][j] = std::max(f[i][j], a[i].P * j + calc(i, q[head]));
}
}
}
std::cout << f[m][n] << '\n';
return 0;
}