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 i
th item to put in your bag, you can't choose the i+p
th 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 remaining
th choice for a segment from the range [start, number of segments]
is either ...
- ... the segment at
start
PLUS the bestremaining - 1
th choice for a segment from the range[start + segment width, number of segments]
, OR - ... the best
remaining
th 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
.
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_57using namespace std;_57using ll = long long;_57_57ll dp[5000][5000];_57_57int 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!