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

5.3.2 Divide-and-Conquer DP Optimization

5-Optimization/5.3.2_Divide-and-Conquer_DP_Optimization.cpp

View on GitHub

Computes one layer of a dynamic programming recurrence whose optimal transition indices are monotone. This optimization applies to recurrences of the form dp_cur[i] = min(dp_prev[k] + cost(k, i)), where the minimizing index for $i$ never decreases as $i$ increases.

The function below computes dp_cur[l..r] by evaluating the midpoint first, then recursing on the left half with a restricted upper bound on the optimal k, and on the right half with a restricted lower bound. The caller is responsible for checking that the recurrence satisfies the required monotonicity condition.

  • compute_dc_layer(dp_prev, dp_cur, l, r, opt_l, opt_r, cost) fills dp_cur[l..r] using candidate transition indices in [opt_l, opt_r]. The template parameter cost must be callable such that cost(k, i) returns the transition cost from previous state k to current state i.

Implementation

#include <algorithm>
#include <cstdint>
#include <vector>

const int64_t INF = (1LL << 62);

template<class Cost>
void compute_dc_layer(
    const std::vector<int64_t> &dp_prev, std::vector<int64_t> &dp_cur, int l, int r, int opt_l,
    int opt_r, Cost cost
) {
  if (l > r) {
    return;
  }
  int mid = l + (r - l) / 2;
  int64_t best = INF;
  int best_k = opt_l;
  int upper = std::min(mid, opt_r);
  for (int k = opt_l; k <= upper; k++) {
    int64_t candidate = dp_prev[k] + cost(k, mid);
    if (candidate < best) {
      best = candidate;
      best_k = k;
    }
  }
  dp_cur[mid] = best;
  compute_dc_layer(dp_prev, dp_cur, l, mid - 1, opt_l, best_k, cost);
  compute_dc_layer(dp_prev, dp_cur, mid + 1, r, best_k, opt_r, cost);
}

Example Usage

#include <cassert>
using namespace std;

struct SquareSegmentCost {
  vector<int64_t> prefix;

  explicit SquareSegmentCost(const vector<int> &a) : prefix(a.size() + 1) {
    for (int i = 0; i < static_cast<int>(a.size()); i++) {
      prefix[i + 1] = prefix[i] + a[i];
    }
  }

  int64_t operator()(int k, int i) const {
    int64_t sum = prefix[i] - prefix[k];
    return sum * sum;
  }
};

int main() {
  vector<int> a{1, 2, 3, 4};
  int n = static_cast<int>(a.size());
  SquareSegmentCost cost(a);

  vector<int64_t> dp_prev(n + 1, INF), dp_cur(n + 1, INF);
  dp_prev[0] = 0;
  compute_dc_layer(dp_prev, dp_cur, 1, n, 0, n - 1, cost);
  assert(dp_cur[4] == 100);  // One group: (1 + 2 + 3 + 4)^2.

  vector<int64_t> dp_two(n + 1, INF);
  compute_dc_layer(dp_cur, dp_two, 1, n, 0, n - 1, cost);
  assert(dp_two[4] == 52);  // Split as [1, 2] and [3, 4].
  return 0;
}