HDU1561 The more, The Better(树形背包)

通过这道题对树形背包理解更深一步......

有几个地方需要注意:

1.本题数据结构为森林,需增加一个超根作为根节点,M+=1(后面解释)。

2.本题有拓扑序的限制,通过vector建成的一棵树中,必须父节点选择了之后才可以往下选择孩子节点。

3.在以v为根的子树中选择k个节点,k必然小等于v的子树大小,所以dfs过程中维护子树大小并进行优化,效率会有很大提升。

关键在于dp过程中如何保证第2点:

dp[u][j]表示在u为根的子树中选择j个点所得最大值,将边界条件设为dp[u][1]=val[u],这样就保证了在u为根的子树中选择1个点时只能是选择他自己,这样就能解决拓扑序的限制,我们的求解目标就是dp[0][M],注意这个M是加1过的,因为我们增加一个超根,我们只有在选择超根后才能往下选,所以M要加1,且超根是没有权值的,所以对最后答案也就没有影响。

 

如何理解树形背包:回想背包的特点是什么,枚举i物品时前i-1个已经求解,所以相当于是在原来的基础上在更新最优解(这也是大多数DP的特点),我们在处理u节点时,首先求解到一棵子树v1,通过v1更新了dp[u][ ],那么后面的v2,v3...也就基于前面的结果再次求解,最后能得到dp[u][ ]的最优解,代码类似于分组背包,还要注意倒推,因为每个节点只能被选1次。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<vector>
 5 using namespace std;
 6 const int N=200+10;
 7 int val[N],dp[N][N];
 8 vector<int>E[N];
 9 
10 int dfs(int u,int M){//返回以u为根的子树大小 
11     dp[u][1]=val[u];
12     int sizeu=1,sizev=0;
13     for(int i=0;i<E[u].size();i++){
14         int v=E[u][i];
15         sizev=dfs(v,M-1);
16         for(int j=M;j>=1;j--)//类似分组背包倒推 
17             for(int k=1;k<=sizev&&k<j;k++)
18                  dp[u][j]=max(dp[u][j],dp[v][k]+dp[u][j-k]);
19         sizeu+=sizev;
20     }
21     return sizeu;
22 }
23 
24 int main(){
25     int N,M;
26     while(~scanf("%d%d",&N,&M),N+M){
27         for(int i=0;i<=N;i++) E[i].clear();
28         memset(dp,0,sizeof(dp));
29         M++;//增加超根后,M+1 
30         val[0]=0;
31         int u;
32         for(int i=1;i<=N;i++){
33             scanf("%d%d",&u,&val[i]);
34             E[u].push_back(i);
35         }
36         dfs(0,M);
37         printf("%d\n",dp[0][M]);
38     }
39     return 0;
40 }

 

posted @ 2022-06-18 16:38  YHXo  阅读(24)  评论(0编辑  收藏  举报