Editorial : Deterministic Heap (Easy Version)
For anyone else wondering about where the binomial coefficients come from, here’s an alternate explanation.
Let’s try to find the number of unique max heaps using the
Let
The first thought that comes to mind is to perform the first operation on a random node, and then recurse for the same tree, with
However, this leads to overcounting. Consider
- Configuration 1 : Node
is chosen in round 1, and it’s child is chosen in round 2. - Configuration 2 : Node
is chosen in round 1, and it’s parent is chosen in round 2.
Notice that if
So, let’s call the number of participation rounds for each node as
So the number of unique max heaps is the non-negative integral solution for this equation, which is pretty well known via Stars and Bars to be
First, let’s find out the number of unique max heaps using the
Then, from the remaining number of operations, we need to distribute some operations to the left and right subtree. How many operations can left receive?
Note that
Now, how do we count unique deterministic max heaps? The first thought that comes to mind is to track the final value of each node, something like
Then, you might think about blocking the transitions when
But since 3D DP will be hard to visualize and optimize, let’s remove the boolean parameter, and maintain two 2D DP. Define
To perform the transitions,
If
But if
There are
How to optimize it? Before we do that, let’s consider the problem of finding the sum of pairwise product in an array
You immediately know a 1D DP approach. But can you optimize this 2D DP. Why or why not?
for gap in [1, n]:
for i in [1, n]:
j = i + gap
if j <= n:
ans += a[i]*a[j]
Optimization is not possible, because when a gap is fixed, there is no pattern, or any number that remains constant. However, we didn’t really need to fix the gap, we could’ve fixed
We will use the same trick here. Instead of fixing
for left in [0, k]:
remain = k - left:
for right in [0, remain]:
outdp[n][k] += outdp[n - 1][left]*outdp[n - 1][right]
for right in [0, min(left - 1, remain)]:
indp[n][k] += indp[n - 1][left]*outdp[n - 1][right]
for right in [left + 1, remain]:
indp[n][k] += outdp[n - 1][left]*indp[n - 1][right]
The time complexity is now
It’s the same as the transitions in my DP, except it does the heavy lifting through combinatorics (which isn’t really required, it can be offloaded to DP).
Notice that my
If you don’t want to compute it explicitly, just remove the
for left in [0, k]:
remain = k - left:
for right in [0, min(left - 1, remain)]:
indp[n][k] += indp[n - 1][left]*outdp[n - 1][right]
for right in [left + 1, remain]:
indp[n][k] += outdp[n - 1][left]*indp[n - 1][right]
and let’s remove all keywords that reference
for left in [0, k]:
remain = k - left:
for right in [0, min(left - 1, remain)]:
dp[n][k] += indp[n - 1][left]*f(n - 1, right)
for right in [left + 1, remain]:
dp[n][k] += f(n - 1, left)*dp[n - 1][right]
The editorial’s transitions are written in a fancy way using
If you want to visualize how trees are computed using editorial’s, you can just visualize how trees are computed using left and right variables above, and apply the same thing there. The crucial idea here is that since you are calculating indp, one of the child would go to outdp, and one would remain for indp, hence that’s why you see the product of binomial coefficients with DP. If you understand the above DP, you should be able to understand editorial’s DP as well. Let me know if anything specific that you’re confused about.