單調佇列最佳化 DP

Unino發表於2024-10-01

單調佇列可以 \(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;
}

相關文章