题意:
给一个具有n个节点的树,每个节点上有一个值v,现在从节点1开始,走L步,问L步中经过的v值之和最大为多少。若一个点被重复走过,这个权值只计算一次。
范围:
1<n<=50,0<=L<=100,0<=v<=100。
树形DP解决,状态分析:
dp[u][1][step]:表示从节点u开始,走step步,此时回到u点,经过的点的最大权值之和。
dp[u][0][step]:表示从节点u开始,走step步,此时没有回到u点,经过的点的最大权值之和。
状态转移:
走step步回到u点,只有一种转移方法:child表示当前的子树,我们都从当前子树考虑,若能转移,当前子树也必须回到child点。所以转移方程为:
dp[u][1][step]=max(dp[u][1][step],dp[u][1][i]+dp[child][1][step-i-2]):dp[u][1][i]从前面的几个孩子求得的值,dp[child][1][step-i-2]当前孩子必须回到child点,-2表示u->child的边,来回的步数。
走step步没有回到u点,有两种转移方法:也是从当前子树考虑。若能转移,可以有两种情况:
①当前子树回到child点,前面考虑的子树没有回到u点;
dp[u][0][step]=max(dp[u][0][step],dp[u][0][i]+dp[child][1][step-i-2])
②当前子树没回到child点,前面考虑的子树回到了u点。
dp[u][0][step]=max(dp[u][0][step],dp[u][1][i]+dp[child][0][step-i-1])
大体思路如上,具体实现细节需要注意一下边界情况,还有在用一棵子树转移的时候,为了防止这棵子树跟自己本身的转移,导致有些点被重复采集,这里采用了tmp[][][]数组存储了当前子树更新了的一些状态,防止这些更新的状态被本棵树再次利用,这里主要是这目的,然后在对这棵树转移结束后,在用tmp数组更新dp数组。如下:
tmp[u][1][i]=max(tmp[u][1][i],dp[u][1][j]+dp[child][1][i-j-2]);tmp[u][0][i]=max(tmp[u][0][i],dp[u][1][j]+dp[child][0][i-j-1]);<pre name="code" class="html">tmp[u][0][i]=max(tmp[u][0][i],dp[u][0][j]+dp[child][1][i-j-2]);
具体代码实现如下:
#include <vector>
#include <list>
#include <map>
#include <set>
#include <deque>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <cstring>
using namespace std;
#define N 55
class CollectingTokens {
public:
int maxTokens(vector <int>, vector <int>, vector <int>, int);
vector<int> edge[N];
int val[N];
int st;
int dp[N][2][105];
int tmp[N][2][105];
void dfs(int u,int v);
void cal(int u,int child);
void Copy(int u);
};
void CollectingTokens::Copy(int u)
{
for(int i=0;i<=st;i++)
{
for(int j=0;j<=i;j++)
{
if((i-j-2>=0)&&(i%2==0)&&(j%2==0))
dp[u][1][i]=tmp[u][1][i];
if(i-j-1>=0)
dp[u][0][i]=tmp[u][0][i];
if(i-j-2>=0)
dp[u][0][i]=tmp[u][0][i];
}
}
}
void CollectingTokens::cal(int u,int child)
{
for(int i=0;i<=st;i++)
{
tmp[u][1][i]=dp[u][1][i];
tmp[u][0][i]=dp[u][0][i];
for(int j=0;j<=i;j++)
{
if((i-j-2>=0)&&(i%2==0)&&(j%2==0))
tmp[u][1][i]=max(tmp[u][1][i],dp[u][1][j]+dp[child][1][i-j-2]);
if(i-j-1>=0)
tmp[u][0][i]=max(tmp[u][0][i],dp[u][1][j]+dp[child][0][i-j-1]);
if(i-j-2>=0)
tmp[u][0][i]=max(tmp[u][0][i],dp[u][0][j]+dp[child][1][i-j-2]);
}
}
Copy(u);
}
void CollectingTokens::dfs(int u,int pr)
{
int len=edge[u].size();
int num=0,child;
dp[u][1][0]=dp[u][0][0]=val[u];
for(int i=0;i<len;i++)
{
child=edge[u][i];
if(child==pr) continue;
num++;
dfs(child,u);
cal(u,child);
}
if(num==0)
{
for(int i=0;i<=st;i++)
{
dp[u][1][i]=val[u];
dp[u][0][i]=val[u];
}
}
}
int CollectingTokens::maxTokens(vector <int> A, vector <int> B, vector <int> tokens, int L) {
int len=A.size();
for(int i=0;i<len;i++)
{
edge[A[i]].push_back(B[i]);
edge[B[i]].push_back(A[i]);
}
for(int i=0;i<len+1;i++)
val[i+1]=tokens[i];
st=L;
memset(dp,0,sizeof(dp));
dfs(1,1);
int ans=0;
for(int i=0;i<=st;i++)
ans=max(ans,max(dp[1][1][i],dp[1][0][i]));
return ans;
}
这篇博客介绍了如何使用树形动态规划解决一个树形问题,即在给定步数L内,从节点1出发,经过的节点值之和最大是多少。文章详细阐述了状态定义、状态转移方程,并提供了实现代码,特别注意了避免重复计算的边界条件处理。

2176

被折叠的 条评论
为什么被折叠?



