467C: George and Job

A variation on the Knapsack problem. To state the problem in knapsack terms, you can fit k items in your bag. Optimize the sum of their values. The twist is the items are actual m-length segments of an array of values. This means if you choose the ith item to put in your bag, you can't choose the i+pth segments for p in {1,2,...,m-1}. Basically choosing one segment excludes choosing all overlapping segments.

We can solve this problem using dynamic programming. The key observation, which I'll credit to my friend Hoang for helping me see, is this: For some start in [0, number of segments] and remaining in [0, number of choices], the best remainingth choice for a segment from the range [start, number of segments] is either ...

  1. ... the segment at start PLUS the best remaining - 1th choice for a segment from the range [start + segment width, number of segments], OR
  2. ... the best remainingth choice for a segment from the range [start + 1, number of segments]

In the picture below, the green blob is the segment at start, the blue blob is the segment at start + 1, and the yellow squiggle represents the bounds of the subproblem when we choose the green blob and recursively solve from start + m, remaining - 1.

An illustration of the bullet points above

Considering these two options for each start and remaining constitutes a recursive strategy for solving the problem. The dynamic programming step is to simply cache the optimal sum for each start and remaining in the problem's state-space.

We can preprocess the original array to get the sum of each segment in O(n). Caching results in a matrix dp[remaining][start] means we can recall solutions to subproblems in constant time. Therefore the complexity of this solution is O(number of segments * number of choices) or O((n-m+1) * k).


_57
#include <iostream>
_57
#include <functional>
_57
#include <numeric>
_57
#include <algorithm>
_57
#include <array>
_57
#include <vector>
_57
_57
using namespace std;
_57
using ll = long long;
_57
_57
ll dp[5000][5000];
_57
_57
int main() {
_57
int n,m,k;
_57
cin >> n >> m >> k;
_57
_57
vector<ll> a (n);
_57
for (auto& x : a) cin >> x;
_57
_57
const int num_segments = n-m+1;
_57
_57
for (int r = 0; r < k; ++r) {
_57
for (int c = 0; c < num_segments; ++c) {
_57
dp[r][c] = -1;
_57
}
_57
}
_57
_57
// preprocessing -- precompute all m-length segment sums.
_57
// segment_from[i] = sum of m consecutive elements of a starting from i, a[i .. i+m-1]
_57
vector<ll> segment_from (num_segments, 0);
_57
ll running_sum = accumulate(a.begin(), a.begin()+m, 0LL);
_57
_57
for (int i = 0; i < num_segments; ++i) {
_57
segment_from[i]=running_sum;
_57
running_sum -= a[i];
_57
running_sum += a[i+m];
_57
}
_57
_57
// the optimal `i`th choice is either
_57
// ... the segment at `start` plus the optimal `i + 1`th choice
_57
// ~~ OR ~~
_57
// ... the optimal `i`th choice starting from `start + 1`
_57
function<ll(int,int)> pick_subseq = [&] (int start, int remaining) {
_57
if (start >= num_segments || remaining < 0) return 0LL;
_57
if (dp[remaining][start] != -1) return dp[remaining][start];
_57
ll alternative = pick_subseq(start+1, remaining);
_57
ll best_next = pick_subseq(start+m, remaining-1);
_57
dp[remaining][start] = max(alternative, segment_from[start] + best_next);
_57
return dp[remaining][start];
_57
};
_57
_57
ll ans = pick_subseq(0, k-1);
_57
_57
cout << ans << '\n';
_57
_57
// thanks for helping me with this problem, Hoang!
_57
}

If you found this solution helpful, consider leaving a star!