The Hardest Easy DP problem
You are given an array consisting of $n$ positive integers. You are standing at the first index, and you want to reach the last index. You can make as many forward jumps as you want. If you jump from index $i$ to $j$ your profit is $(j - i) \cdot a[i]$. What is the maximum total sum of profits that you can achieve?
The constraints are $n \leq 10^3$.
This looks like a classical textbook DP problem (and it is). Let’s define $dp[i]$ to be the maximum profit when you start your journey at index $i$. The answer to the original problem is then $dp[0]$.
For the transitions, you can try making a jump to all the indices to the right, and recursively pick the optimal jump that gives you the maximum profit.
$$ dp[i] = \text{max} \{(j - i) \cdot a[i] + dp[j] \} \qquad j > i $$
// dp[i] is the maximum score when you start at index i.
vector<long long> dp(n, 0);
for (int i = n - 1; i >= 0; i--) {
for (int j = i + 1; j < n; j++) {
dp[i] = max(dp[i], 1LL * (j - i) * a[i] + dp[j]);
}
}
return dp[0];
The time complexity of this DP is $\mathcal{O}(n^2)$. But can we optimize it to something better, like $\mathcal{O}(n \cdot log (n))$?
And here’s the interesting part : this optimization is harder than you think or easier than you think, depending upon your skill level. I’ve asked several people this problem, and it’s usually the smarter ones (in terms of competitive programming) who have a hard time coming up with the optimization.
If you’ve watched my video on Optimizing DP with Sliding Window, you might think of separating the $i$ and $j$ terms, and then using sets/heaps/priority-queues to quickly compute terms dependent on $j$.
$$ dp[i] = \text{max} \{j \cdot a[i] - i \cdot a[i] + dp[j] \} \qquad j > i $$
Notice that $i \cdot a[i]$ is constant for a fixed $i$. So we only need a data structure for fast computation of $j \cdot a[i] + dp[j]$. Problem is, not even a lazy segment tree can do this computation. So how do we optimize it then?
But if I show you the optimization, you’ll be unable to unsee it. Hindsight is 20/20, so I want you to think hard about the optimization before reading the next part.
In the remaining part of the blog, I’ll show you how to arrive at it intuitively.