题意:
Problem Description
ACboy很喜欢玩一种战略游戏,在一个地图上,有N座城堡,每座城堡都有一定的宝物,在每次游戏中ACboy允许攻克M个城堡并获得里面的宝物。但由于地理位置原因,有些城堡不能直接攻克,要攻克这些城堡必须先攻克其他某一个特定的城堡。你能帮ACboy算出要获得尽量多的宝物应该攻克哪M个城堡吗?
分析:与前面的倆道树形dp相比,本题目多了一个限制条件,就是选M个节点,这个很自然的联想到了背包问题,那么要如何在树上做一个01背包呢?
先不考虑森林的情况,对于当前这一棵树,就是由以当前节点为根的树出现过的状态跟一棵子树上出现过的状态进行组合
for(int j=t;j>=1;j--)
{
for(int k=1;k<=now;k++)
dp[u][j+k]=max(dp[u][j+k],dp[u][j]+dp[w][k]);
}
//个人觉得t和now这俩个变量才是这题目的关键,t为当前遍历过的节点总数,不包含以w为根的树
//这俩个变量主要是处理哪些状态会影响到当前状态的转移的问题。
//换句话说,在当前状态下,就是以u为根的树,要选上以w为根的树上的城堡,那么,能出现的状态就是由
//以u为根的树上出现过的状态数t跟w为根的树上出现过的状态数now组合出选上之后的状态
//可能解释的有点累赘了
{
for(int k=1;k<=now;k++)
dp[u][j+k]=max(dp[u][j+k],dp[u][j]+dp[w][k]);
}
//个人觉得t和now这俩个变量才是这题目的关键,t为当前遍历过的节点总数,不包含以w为根的树
//这俩个变量主要是处理哪些状态会影响到当前状态的转移的问题。
//换句话说,在当前状态下,就是以u为根的树,要选上以w为根的树上的城堡,那么,能出现的状态就是由
//以u为根的树上出现过的状态数t跟w为根的树上出现过的状态数now组合出选上之后的状态
//可能解释的有点累赘了
dp[u][j]表示以u为根的树,选了j个子节点的最优解
#include<iostream> #include<algorithm> #include<vector> using namespace std; int v[201],dp[201][201],f[201],n,m; vector<int> G[201]; int dfs(int u) { dp[u][1]=v[u];//这里表示父节点一定会选 int t=1; int size=G[u].size(); for(int i=0;i<size;i++) { int w=G[u][i]; int now=dfs(w);//深度搜索,计算以w为根的树上的01背包 for(int j=t;j>=1;j--) { for(int k=1;k<=now;k++) dp[u][j+k]=max(dp[u][j+k],dp[u][j]+dp[w][k]); } //个人觉得t和now这俩个变量才是这题目的关键,t为当前遍历过的节点总数,不包含以w为根的树 //这俩个变量主要是处理哪些状态会影响到当前状态的转移的问题。 //换句话说,在当前状态下,就是以u为根的树,要选上以w为根的树上的城堡,那么,能出现的状态就是由 //以u为根的树上出现过的状态数t跟w为根的树上出现过的状态数now组合出选上之后的状态 //可能解释的有点累赘了 t+=now; } return t; } int main() { while(scanf("%d %d",&n,&m)==2&&(m||n)) { int b; for(int i=0;i<=n;i++) { f[i]=i; G[i].clear(); } for(int i=1;i<=n;i++) { scanf("%d %d",&b,&v[i]); if(b!=0) { G[b].push_back(i); f[i]=b; } } v[0]=0; for(int i=1;i<=n;i++)//将森林转化为树 { if(f[i]==i) { G[0].push_back(i);//0 节点作为根节点 } } memset(dp,0,sizeof(dp)); dfs(0); printf("%d\n",dp[0][m+1]);//因为多了编号为0的城堡,所以总共攻打了m+1个城堡 } return 0; }