Alex's Anthology of Algorithms Common Code for Contests in Concise C++
Optimization / Dynamic Programming Optimization

5.3.1 Monotone Queue DP Optimization

5-Optimization/5.3.1_Monotone_Queue_DP_Optimization.cpp

View on GitHub

Maintains the minimum or maximum value in a sliding window using a monotone queue. This is useful for dynamic programming recurrences where each transition may only come from one of the last $w$ states, such as dp[i] = a[i] + min(dp[j]) for $j \in [i - w, i - 1)$.

The queue stores candidate (index, value) pairs in monotone order. Expired indices are removed from the front, and dominated values are removed from the back before inserting a new candidate.

  • MonotoneQueue<Compare>() constructs an empty queue. Use std::less<T> for minimum queries and std::greater<T> for maximum queries.
  • push(index, value) inserts candidate value at position index, removing dominated candidates.
  • expire(first_valid) removes candidates with index less than first_valid.
  • top() returns the best active (index, value) pair.
  • empty() returns whether the queue has no active candidates.

Implementation

#include <deque>
#include <functional>
#include <utility>

template<class T, class Compare>
class MonotoneQueue {
  std::deque<std::pair<int, T>> q;
  Compare better;

 public:
  bool empty() const { return q.empty(); }

  void push(int index, const T &value) {
    while (!q.empty() && !better(q.back().second, value)) {
      q.pop_back();
    }
    q.emplace_back(index, value);
  }

  void expire(int first_valid) {
    while (!q.empty() && q.front().first < first_valid) {
      q.pop_front();
    }
  }

  std::pair<int, T> top() const { return q.front(); }
};

Example Usage

#include <cassert>
#include <vector>
using namespace std;

int main() {
  vector<int> a{4, 2, 7, 1, 3, 6};

  MonotoneQueue<int, less<int>> minq;
  vector<int> window_min;
  for (int i = 0; i < static_cast<int>(a.size()); i++) {
    minq.push(i, a[i]);
    minq.expire(i - 2);
    if (i >= 2) {
      window_min.push_back(minq.top().second);
    }
  }
  assert(window_min[0] == 2);
  assert(window_min[1] == 1);
  assert(window_min[2] == 1);
  assert(window_min[3] == 1);

  // dp[i] = a[i] + min(dp[j]) over max(0, i - 2) <= j < i.
  vector<int> dp(a.size());
  MonotoneQueue<int, less<int>> best;
  dp[0] = a[0];
  best.push(0, dp[0]);
  for (int i = 1; i < static_cast<int>(a.size()); i++) {
    best.expire(i - 2);
    dp[i] = a[i] + best.top().second;
    best.push(i, dp[i]);
  }
  assert(dp[5] == 13);
  return 0;
}