DP on spanning subgraphs of a tree
Find the number of ways to arrange
distinct persons.
We all know the answer
Define
This idea of extending current states to create new ones is pretty common in DP problems (and is the crux of the algorithms that follow). You can also read this tutorial that applies the idea to binary strings : Extending Binary Strings with DP
A spanning subgraph contains all vertices from the parent graph and a subset of edges from the parent graph. Given a tree, count the number of spanning subgraphs that can be created.
There are
Given a tree, count the number of spanning subgraphs in which the root vertex has degree exactly equal to
.
Define
In each of the spanning subgraph, the root vertex can have degrees ranging from
Consider any node
Start out with an isolated vertex
Consider all configurations of the subtree of
- In the new configuration, we can choose to delete the edge between
and . Hence, if there are ways to achieve the left config independently and ways to achieve the right config independently, there would be
ways to merge those configurations into one.
How does the merged configuration look like? Since we deleted the edge leading to child
- Or we could keep the edge leading to child
. However, even in this case, the number of new configurations would be
How does the merged configuration look like? In the new configuration, the degree of
In the above image, you can see that we’ve already accumulated 2 children so far, and we want to investigate what happens when the third child is introduced. All of the 4 configuration on the top can be concatenated with 2 configuration from the bottom to create a new configuration. In this new configuration, if the we keep the purple edge (leading from orange to green cell), the degree of orange cell increases by 1. But if we decide to leave the purple edge, the degree remains the same.
When we try to introduce a child
Hence, we need an auxillary dp array, conveniently called
This is another common technique in DP problems which avoids losing data of older states before ALL new states are computed correctly.
dfs(u, par):
dp[u][0] = 1
for c in adj[u]:
if c == par:
continue
dfs(c, u)
ndp = []
for d in range(adj[u].size):
for now in range(adj[c].size):
ndp[d] += dp[src][d] * dp[child][now];
ndp[d + 1] += dp[src][d] * dp[child][now];
swap(dp[u], ndp)
You can find the full code here
Given a tree, calculate the sum of
over all spanning subgraph.
Original Problem which boils down to this subproblem
Warning: Reducing the problem to this subproblem is a difficult task. If you are not able to understand the editorial, skip it for now and just try to implement the reduced problem.
Define
How do we extend a subgraph by introducing a new child? Consider all configurations of the subtree of
- In the new configuration, we can choose to delete the edge between
and . But now, for the new configuration, it is not at all clear how to recompute the new values. Blindly taking product like last time shouldn’t work (or would it?), because the value from the left and right configuration needs to be removed and a new needs to be added. All of this looks complex, because we don’t really store the individual of each configuration, we just store the sum of .
But things would become simple, once you wonder what about what’s inside those summation terms. Suppose a total of 3 configurations are possible from the
Now, let us investigate what happens when we try to create new configurations.
From the the left half, we could choose the
We follow a similar strategy if we were to choose
or if we were to choose
Now, the new DP value would be sum of all these 6 values. So, let’s group them and sum them up.
Once we take
Hence, the product strategy introduced in the counting problem works here as well.
How does the merged configuration look like? Since we deleted the edge leading to child
- Or we could keep the edge leading to child
. This is where things get tricky.
From the the left half, we could choose the
Similary, the incoming child is going to retain the edge between
We repeat the same arguments for
We repeat the same arguments for
Combining them as before, we get
Hence, the product logic from before still works, but with some corrections. Of course, this new contribution should be provided to the configuration with degree of
dfs(u, par):
dp[u][0] = 1
for c in adj[u]:
if c == par:
continue
dfs(c, u)
ndp = []
for d in range(adj[u].size):
for now in range(adj[c].size):
ndp[d] += dp[src][d] * dp[child][now]
ndp[d + 1] += dp[src][d] * dp[child][now] * (d + 1) * (now + 1)
swap(dp[u], ndp)
You can find the full code here
The time complexity is
Resources for the proof can be found here. I will add my own interpretation later.
The idea of extending a tree by adding children incrementally is very useful while solving Tree DP problems. Even though you might never come across problems asking you to compute sum of
Given a tree, calculate the sum of
over all spanning subgraph.
If you’ve really understood the concepts discussed : DP on subgraphs, extension and correction phase, ndp, etc, try to implement this problem on your own. It is not straight-forward, but doable.
My initial plan was to add this problem as an example and add the factorial one as an exercise, since this was just sum over sum, how hard could it be as compared to sum over prod over factorial. But I was wrong. This one is more difficult. Can you guess the reason why? Let me know your answers on this Codeforces Blog. Or if you have an alternate easier approach, do let me know on the CF blog.
I will add my solution and explanation after some AC submissions.