diff --git a/content/5_Plat/DC-DP.mdx b/content/5_Plat/DC-DP.mdx index d056f8874b..1260e872ac 100644 --- a/content/5_Plat/DC-DP.mdx +++ b/content/5_Plat/DC-DP.mdx @@ -8,9 +8,7 @@ prerequisites: - convex-hull-trick --- -Allows you to reduce $\mathcal{O}(N^2)$ to $\mathcal{O}(N\log N)$. - -## Tutorial +## Overview -## Example - Circular Barn +Consider a dynamic programming problem with the following formula - +$$ +dp(i,j) = \min_{0\leq k \leq j} ( dp(i-1, k-1) + C(k,j)), +$$ -You should already be familiar with the [CHT solution](/plat/convex-hull-trick#problems). +where $C(i,j)$ is a cost function and you can compute it in $O(1)$ time. +Furthermore, $dp(i,j) =0$ for $j<0$. + +The straightforward implementation gives a runtime of $O(MN^2)$ if $0\leq i < M$ and $0\leq j < N$. +Divide & Conquer DP allows this to be optimized to $O(M N \log N)$. + +For each $i,j$, let $\text{opt}(i,j)$ be the value of $k$ that minimizes the right hand side of the equation. +Divide & Conquer DP **only applies if** + +$$ +\text{opt}(i,j) \leq \text{opt}(i,j+1). +$$ - +Often, proving this with the given cost function is challenging, +but if the cost function satisfies the [quadrangle inequality](https://codeforces.com/blog/entry/86306), the condition holds. -Add analysis later. +We can then apply the idea behind Divide & Conquer. +Fix a given $i$. First, compute $\text{opt}(i,n/2)$. +Then compute $\text{opt}(i, n/4)$ using the fact that it is less than or equal to $\text{opt}(i, n/2)$. +Similarly, we can compute $\text{opt}(i, 3n/4)$ and recursively split the ranges in half, keeping track on the lower and upper bounds. - +Since each possible value of $\text{opt}(i, j)$ appears $O(\log n)$ times, this gives a final runtime of $O(mn \log n)$. + +## Example - Circular Barn + + + +You should already be familiar with the [CHT solution](/plat/convex-hull-trick#problems). -Check the -[official editorial](http://www.usaco.org/current/data/sol_cbarn_platinum_feb16.html). +### Explanation + +We iterate through the possibilities of the location of the first door. +For each of the first doors, we can now view the barn linearly. +All further calculations will be done assuming the barn is a linear sequence of doors starting at the first opened door. + +Let $dp(i,k)$ denote the location of the last door if we place $k$ doors optimally among the first $i$ rooms. +The idea is that $dp(i,k) \leq dp(i+1, k)$. +Assume this was not true for the sake of contradiction. +Then $dp(i,k) > dp(i+1,k)$, so we also have $dp(i+1,k) \leq i$. +But then we could have used the best possible setup for $(i+1,k)$ in the $(i,k)$ setup as well, since all open doors are among the first $i$ rooms anyways. + +Since the monotonicity condition is held, we can now perform Divide & Conquer DP. +Fix the value of $k$ and compute $dp(n/2, k)$. +Then compute it for the left and right halves of the array. + +### Implementation + +**Time Complexity:** $\mathcal{O}(n^2 k \log n)$, since we need to check $n$ rooms for the optimal first barn position. + + + + +```cpp +#include +using namespace std; +#define ll long long + +const int MAX_N = 1000; +const int MAX_K = 7; + +// calc[i][j] stores the # of steps to get all cows distance j away to door i +// to make implementing a cyclic array easier, we double the size +vector> calc(2 * MAX_N, vector(MAX_N + 1, 0)); +// dp[i][j] stores the answer for doors 0,1,.., j and i doors open +vector> dp(MAX_K + 1, vector(MAX_N + 1, INT64_MAX)); + +int rot; + +void compdp(int k, int begin, int end, int rl, int rr) { + // fixed k, begin and end are the ends of the array, rl and rr are the + // bounds on the last door used + int mid = (begin + end) / 2; + + ll best = INT64_MAX; + int best_last = -1; + // best is min amount moved, last is the last door used + for (int last = rl; last <= min(mid, rr); last++) { + ll cost = dp[k - 1][last - 1] + calc[last + rot][mid - last + 1]; + if (cost < best) { + best = cost; + best_last = last; + } else if (cost == best && last < best_last) { + best_last = last; + } + } + dp[k][mid] = best; + if (begin == end) { return; } + compdp(k, begin, mid, rl, best_last); + compdp(k, mid + 1, end, best_last, rr); +} + +int main() { + freopen("cbarn.in", "r", stdin); + int n, k; + cin >> n >> k; + + vector a(2 * n); + for (int i = 0; i < n; i++) { + cin >> a[i]; + a[i + n] = a[i]; + } + + for (int i = 0; i < n; i++) { + for (int j = 1; j <= n; j++) { + calc[i][j] = calc[i + n][j] = + calc[i][j - 1] + (ll)a[i + j - 1] * (j - 1); + } + } + + // rotate stores where we start in the linear representation + ll ans = INT64_MAX; + for (rot = 0; rot < n; rot++) { + for (int i = 0; i < n; i++) { dp[0][i] = calc[rot][i + 1]; } + for (int i = 1; i < k; i++) { + for (int j = 0; j < n; j++) { dp[i][j] = INT64_MAX; } + compdp(i, i, n - 1, i, n - 1); + } + ans = min(ans, dp[k - 1][n - 1]); + } + + freopen("cbarn.out", "w", stdout); + cout << ans << endl; +} +``` + + + ## Problems @@ -54,6 +171,8 @@ that you swap do not count towards the minimum number of bubble sort swaps.