Noting that the tree is not rooted, it differs from the knapsack problem on a rooted tree, i.e. TPK, in only that the root has to be chosen in TPK, if possible. So one of the insights I didn't come up with in order to accommodate TPK is to iteratively find and use the "central" node of the tree as root, and solve the rooted sub-problem recursively. Then, if it takes O(N) to solve a TPK for a tree with N nodes, the overall complexity will be O(N log(N)) instead of N^2! Gladly I came up with my own O(N) algorithm to find central node after the contest, so it is a good exercise to think of one by yourself. To clarify, a central node has none of its child subtrees sized more than N/2.
The main reason I write this article is the algorithm that solves TPK in O(Nh), where h is the bag capacity. During contest I did find a paper describing a DFS-based DP algorithm, which I adapted to keep D[u][k][b], meaning the maximum value achievable with capacity b if every node on the path from root to node u are chosen, and all other nodes up to u in DFS traversal order, plus the the k-th child subtree of u are considered. When k is 0 no child subtree of u is considered. For anyone interested, it is described in the paper "On Knapsacks, partitions, and a new dynamic programming technique for trees" by D. S. Johnson and K. A. Niemi.
What is interesting is the solution I saw after the contest at https://www.hackerrank.com/contests/w15/challenges/a-knapsack-problem/editorial, which according to the problem setter keeps D[u][b] to indicate the maximum value attainable up to node u in the DFS traversal order with capacity b. The recurrence is surprisingly simple:
if (size[u] <= b) {
f[u][b]=MAX(f[u-1][b-size[u]]+value[u], f[u][b]); // (1)
}
f[u+subtree_size[u]-1][b]=MAX(f[u+subtree_size[u]-1][b], f[u-1][b]); // (2)
Here u-1 is the node traversed right before u.
Does the recurrence make sense? The second update is very obvious -- if we don't take node u, then the whole subtree rooted at u is not taken. However, the first update doesn't sound correct to me -- if we take node u, then we have to make sure all ancestors of u are taken too, but f[u-1][b-size[i]] doesn't imply it, which is simply the optimal value obtainable when considering up to the node before u!
With confusion, I created a simple test case and check D[][] values for some intermediate nodes. In particular I created a first tree and appended one more node at the end of its DFS traversal to get the second tree. Then I ran the algorithm, and found the D[][] values for a node present in both tree differ!
So obviously the interpretation of D[][] by the problem setter is not precise, if his/her solution actually works. Eventually, I'm able to shown this magical recurrence does work.
Lemma: If u is the last traversed node by DFS, then D[u][b] is the optimal value that could be collected from the tree with bag capacity b.
Proof:
We will show by induction on N, the tree size, as induction are close to DP in nature. Suppose the Lemma holds for every tree with up to N-1 nodes. The case that the root has only one child is trivial -- we could descend from root until we hit a node with multiple children and reduce the problem. So assume the root has multiple children, and consider the last child u of root traversed by DFS. Let t(u) be the subtree rooted at u. Let T be the original tree and by removing t(u) from T we get T1 which ends at u-1, the node visited by DFS right before u.
By induction, D[u-1][b] is the optimal value for T1. Now the only subtree_size affected by adding t(u) is subtree_size[root], which doesn't matter to us. So we know D[x][] doesn't change for any x between the root and u-1, inclusive, and we will show D[v][b] is the optimal value for T, where v is the last node in T.
How will D[v][b] get updated? By (2) in the recurrence it is at least D[u-1][b] -- sure, if we don't take u, then D[u-1][b] is our answer and by induction we know it is correct. In fact this is the only direct (2) update on t(u) from T1 because by DFS nature no subtree rooted in T1 ends in a node in t(u). The only remaining influence of T1 on t(u) is the update of D[u][b] based on (1).
If b<size[u], (1) couldn't carry out, so all nodes in t(u) has no D value except v which has D[v][b]=D[u-1][b]. This is expected, because the whole t(u) couldn't be taken since size[u] exceeds capacity b.
Q.E.D.
2 comments:
Bravo Manager Tsai!
Thanks Master Hu.
Post a Comment