SRM 666 div2 problem999WalkOverATreeDiv2
Problem Statement
Surya has a tree with n nodes, numbered 1 through n. Each node contains some arbitrary nonnegative number of tokens.
Surya sometimes goes for a walk on the tree. He has to start his walk in node 1, but he may terminate it in any node of the tree. Surya gets tired easily: during the walk he is only able to traverse at most L edges.
Surya now wants to collect as many tokens as possible during a single walk. He can collect tokens in all nodes he visits, including the nodes where he starts and ends his walk. Obviously, the tokens in each node can only be collected once.
You are given the structure of the tree in int[]s A and B, each with n-1 elements. For each valid i the tree contains an edge between the nodes A[i] and B[i]. You are also given the int[] tokens with n elements. For each valid i, tokens[i] is the number of tokens in node i+1. Finally, you are given the int L.
Return the maximum number of tokens Surya can collect.
Definition
- ClassWalkOverATreeDiv2
- MethodmaxTokens
- Parametersvector<int> , vector<int> , vector<int> , int
- Returnsint
- Method signatureint maxTokens(vector<int> A, vector<int> B, vector<int> tokens, int L)
Limits
- Time limit (s)2.000
- Memory limit (MB)256
Constraints
- n will be between 1 and 50, inclusive.
- A and B will contain exactly n-1 elements each.
- Each element of A and B will be between 1 and n, inclusive.
- A and B will define a tree.
- tokens will contain exactly n elements.
- Each element of tokens will be between 1 and 100, inclusive.
- L will be between 1 and 100, inclusive.
Test cases
-
- A{ 1 }
- B{ 2 }
- tokens{ 7, 1 }
- L6
Returns 8This tree consists of two nodes and a single edge. There are 7 tokens in node 1 and 1 token in node 2. Surya can make at most six steps, which is more than enough to collect all 7+1 = 8 tokens. -
- A{ 3, 1 }
- B{ 2, 2 }
- tokens{ 2, 3, 9 }
- L5
Returns 14 -
- A{ 1, 2, 5, 3 }
- B{ 4, 4, 1, 4 }
- tokens{ 6, 1, 6, 4, 4 }
- L3
Returns 16This is a tree with five nodes. One optimal walk for this tree is to start in node 1, go to node 4, then to node 3, and then back to node 4. As L=3, this is the longest walk Surya may make. During this walk he will collect 6 tokens in node 1, 4 tokens in node 4, 6 tokens in node 3, and then 0 tokens when revisiting node 4. The total is 6+4+6+0 = 16 tokens. Another optimal walk is to start in node 1, go to node 4, then to node 3, and to stop there. Surya is not required to make all L steps. -
- A{ 9, 1, 7, 10, 5, 8, 3, 4, 2 }
- B{ 6, 6, 9, 6, 6, 1, 1, 6, 6 }
- tokens{ 4, 2, 1, 6, 3, 7, 8, 5, 2, 9 }
- L4
Returns 26 -
- A{ 25, 22, 35, 42, 40, 9, 32, 12, 37, 44, 23, 1, 24, 28, 20, 4, 26, 33, 11, 48, 34, 6, 16, 50, 46, 17, 8, 43, 18, 30, 31, 36, 39, 13, 10, 45, 3, 47, 15, 2, 29, 19, 7, 14, 41, 49, 38, 27, 21 }
- B{ 5, 5, 25, 25, 25, 42, 25, 40, 5, 35, 25, 32, 42, 9, 32, 23, 40, 25, 20, 33, 26, 37, 12, 1, 48, 24, 22, 25, 11, 24, 48, 34, 18, 9, 50, 42, 16, 40, 1, 10, 47, 22, 48, 44, 48, 1, 4, 46, 47 }
- tokens{ 6, 9, 4, 9, 5, 8, 6, 4, 4, 1, 4, 8, 3, 4, 5, 8, 5, 6, 4, 9, 7, 9, 7, 9, 5, 2, 7, 2, 7, 7, 5, 9, 5, 8, 5, 7, 1, 9, 3, 9, 3, 6, 4, 5, 5, 4, 7, 9, 2, 2 }
- L48
Returns 194
题意,给出一个树,每个结点都有一定数量的金币,每个结点的金币只能一次全拿走,拿走就没了,从根结点1出发,最多走l条边,要求最终能得到的最大金币数。
不错的树形dp的题
dp[i][j][0]表示第i结点,走了j条边,最终回到i点的最大值。
dp[i][j][1]表示第i结点,走了j条边,最终不用回到i点的最大值。
则状态转移为 goal为s结点的子结点
dp[s][j][0] = max(dp[s][j][0],dp[s][k][0] + dp[goal][j - k - 2][0]); //表示,前面子结点,回到了s结点,当前goal结点也回到i结点
dp[s][j][1] = max(dp[s][j][1],dp[s][k][1] + dp[goal][j - k - 2][0]); //表示,前面子结点,没回到了s结点,当前goal结点回到i结点
dp[s][j][1] = max(dp[s][j][1],dp[s][k][0] + dp[goal][j - k - 1][1]); //表示,前面子结点,回到了s结点,当前goal结点没回到i结点
注意,要从大到小枚举,因为,这样可 以保证,小的没有被污染,仍是原值。
复杂度为o(n ^ 3)
#define N 105
#define M 100005
#define maxn 205
#define MOD 1000000000000000007
class CollectingTokens
{
public:
bool vis[N];
int dp[N][N][2],n,m;
vector<int> p[N];
vector<int> ts;
void DFS(int s){
For(i,0,m+1) dp[s][i][0] = dp[s][i][1] = ts[s];
FI(p[s].size()){
int goal = p[s][i];
if(!vis[goal]){
vis[goal] = true;
DFS(goal);
for(int j = m;j>=0;j--)
For(k,0,j){
if(j - k >= 2){
dp[s][j][0] = max(dp[s][j][0],dp[s][k][0] + dp[goal][j - k - 2][0]);
dp[s][j][1] = max(dp[s][j][1],dp[s][k][1] + dp[goal][j - k - 2][0]);
}
if(j - k >= 1)
{
dp[s][j][1] = max(dp[s][j][1],dp[s][k][0] + dp[goal][j - k - 1][1]);
}
}
}
}
}
int maxTokens(vector <int> A, vector <int> B, vector <int> tokens, int L)
{
ts.clear();
for(int i = 0;i<tokens.size();i++){
ts.push_back(tokens[i]);
}
n = A.size();m = L;
FI(n+1) p[i].clear();
for (int i = 0; i < n; i++){
A[i]--;B[i]--;
p[A[i]].push_back(B[i]);
p[B[i]].push_back(A[i]);
}
memset(vis,false,sizeof(vis));
vis[0] = true;
DFS(0);
return max(dp[0][m][0],dp[0][m][1]);
}
另一篇题解:
http://blog.csdn.net/lishuandao/article/details/48010589
官方递归题解:
Trees are recursive structures so let's try to find a recursive solution. We will think of how to solve the problem for the subtree rooted at node x , with a given L , what is the maximum number of tokens we can acquire from the nodes in this subtree when starting the walk at x
When dealing with a subtree rooted at x , it will likely have some children. Each of these children is the root of a new subtree, smaller cases for the recursive solution we are designing. The walk that starts at node x can do many things with the child subtrees:
- The walk might ignore the subtree. Maybe the limit L is better spent in the other subtrees.
- The walk might visit the subtree, do some things there and then return back to x .
- The walk might visit the child subtree and not return back to x . This means that this child subtree will be the last subtree visited in the walk. There can only be one subtree of this kind.
This adds a new requirement to our initial idea. Sometimes the solution needs us to return to the subtree root, other times it doesn't. Let's define a function f(x,L,m) that returns the maximum number of tokens in subtree rooted at x we can get using at most L steps; m is 1 if it is necessary that the walk ends at node x (must return).
In solving f(x,L,m) we find a new problem. The whole idea consists of assigning things to do with each of the subtrees of x . Some you don't visit, others you visit in a way that you return to x after the walk and, in case m=0 , it is possible to pick one subtree as the last one so that its walk does not need to return back to x . This introduces an issue: How do you deal with so many subtrees? Each subtree needs to be assigned a decision and also some steps out of the L available ones. The trick is to index all subtrees from 0 to d−1 , the number of children of x is d . We add a new argument e to f : f(x,e,L,m) . The same question as before but now we should ignore the first e children of x . Assume we have already assigned things to those children.
Something that can reduce our trouble is to remove the tokens in node x from the result of f . We know that since we are starting the path, those tokens are acquired anyway, so we do not need to include them in the result. f(x,e,L,m) is the number of additional tokens. When finding the final result we will just add the tokens of the root manually.
Base case: e=d . This means that we are ignoring all of the children of x . x is basically now just a leaf, or has no children. We cannot get any tokens from subtrees that don't exist. The result is 0.
In another case, let the e-th child of x be called y . We have a decision to make regarding the subtree rooted at y . And also a decision to make regarding the remaining subtrees that have an index greater than e:
![](https://i-blog.csdnimg.cn/blog_migrate/8ea5ba41d0d1ae6b100ae47ec3583c61.png)
We have two groups of subtrees to consider, one consists of y , the other of all subtrees with index larger than e . Of the L available steps, we need to reserve some for one of the groups and the rest to the other group. We can do this trying all the options in an O(L) search. Note that if we decide to visit y then we need one or two additional steps for moving from x to y and back (if needed). In detail:
- We decide not to move to y at all. Then we can move on to the next child of x without spending moves: f(x,e+1,L,m) .
- We decide to move to y and to return to x. Allocate s steps to use in the subtree of y . We also use two steps to move from x to y and back. So there will be L−2−s steps available for the other subtrees. Doing this will add the number of tokens in y to the token count: f(y,0,s,1)+f(x,e+1,L−2−s,m)+tokens[y] . Note that since we need to return to y after whatever walk we do, we use m'=1 in that call.
- Or we decide to move to
y
but without coming back to
x
. This can only happen when
m=0
. Once again we assign
s
to this subtree. This time we only need to spend one step to move from
x
to
y
.
y
might be the last subtree we visit, but if
e+1<d
, there will still be other subtrees that we need to assign decisions to. It doesn't matter too much. In reality, we will visit those subtrees before
y
, even though we are making the decision for
y
before theirs. Note that when deciding in those subtrees, you must forcibly return back so that the trip to
y
can happen as planned, so their
m
will be 1:
f(y,0,s,0)+f(x,e+1,L−1−s,1)+tokens[y]
.
The maximum of all those possible options is the result for f(x,e,L,m) .
int n;
vector<int> tokens;
vector<vector<int>> g;
int dp[53][53][101][2];
int f(int x, int e, int L, int mustReturn)
{
int & res = dp[x][e][L][mustReturn];
if (res == -1) {
res = 0;
if (e != g[x].size() ) {
int y = g[x][e];
for (int subL = 0; subL <= L; subL++) {
if (L - subL >= 2) {
// go to y and return
int tm = tokens[y] + f(y, 0, subL, 1) + f(x, e+1, L - subL - 2, mustReturn);
res = std::max(res, tm);
}
if ( (L - subL >= 1) && (mustReturn == 0) ) {
// pick this as the last one
int tm = tokens[y] + f(y, 0, subL, 0) + f(x, e+1, L - subL - 1, 1);
res = std::max(res, tm);
}
}
// don't even bother going to y
int tm = f(x, e+1, L, mustReturn);
res = std::max(res, tm);
}
}
return res;
}
int maxTokens(vector<int> A, vector<int> B, vector<int> tokens, int L)
{
this->tokens = tokens;
// some BFS work to get the tree as a simpler to use structure so that
// g[x] contains a vector of all the children of x:
n = A.size() + 1;
set<int> st;
queue<int> q;
q.push(0);
st.insert(0);
g.resize(n);
while (! q.empty()) {
int x = q.front();
q.pop();
for (int i = 0; i < A.size(); i++) {
int y = -1;
if (A[i]-1 == x) {
y = B[i] - 1;
}
if (B[i]-1 == x) {
y = A[i] - 1;
}
if ( (y != -1) && st.count(y) == 0) {
g[x].push_back(y);
st.insert(y);
q.push(y);
}
}
}
// initialize dp table:
memset(dp, -1, sizeof(dp));
return tokens[0] + f(0, 0, L, 0);
}