problem
ACboy很喜欢玩一种战略游戏,在一个地图上,有N座城堡,每座城堡都有一定的宝物,在每次游戏中ACboy允许攻克M个城堡并获得里面的宝物。但由于地理位置原因,有些城堡不能直接攻克,要攻克这些城堡必须先攻克其他某一个特定的城堡。你能帮ACboy算出要获得尽量多的宝物应该攻克哪M个城堡吗?
Input
每个测试实例首先包括2个整数,N,M.(1 <= M <= N <= 200);在接下来的N行里,每行包括2个整数,a,b. 在第 i 行,a 代表要攻克第 i 个城堡必须先攻克第 a 个城堡,如果 a = 0 则代表可以直接攻克第 i 个城堡。b 代表第 i 个城堡的宝物数量, b >= 0。当N = 0, M = 0输入结束。
Output
对于每个测试实例,输出一个整数,代表ACboy攻克M个城堡所获得的最多宝物的数量。
Sample Input
3 2
0 1
0 2
0 3
7 4
2 2
0 1
0 4
2 1
7 1
7 6
2 2
0 0
Sample Output
5
13
思路
将图转化为森林,如果攻克a城堡必须先攻克b城堡,那么b为a的父亲结点。若要选择一个结点,那么这个结点与该节点的父亲结点都要被选择,而该节点的孩子节点可选也可不选。定义dp[u][i]表示以u为根的子树,选取i个结点所能得到的宝物的最大值。再进行观察,这个动态规划与01背包相似,都是在数量限制的情况下获得的价值最高。可以这样列写转移方程:
dp[u][j]=max(dp[u][j],dp[v][k]+dp[u][j-k])
//1<=j<=M,k<j,v是u的直接孩子结点
//从根出发遍历一次树,在退出一个结点时向其父亲结点更新信息即可。
还有两处优化:
1.数据给的不一定是树,所以要统一加上一个根节点将森林转化为树,并将M+1。
2.dfs记录子树的结点数量为tt,在满足k < j && k<=tt[v]时,才进行转移。
代码示例
#include<bits/stdc++.h>
using namespace std;
const int maxn=210;
int value[maxn];
int dp[maxn][maxn];
vector<int> G[maxn];
int dfs(int u,int M){
dp[u][1]=value[u];
int t=1,tt;
for(int i=0;i<G[u].size();++i){
int v=G[u][i];
tt=dfs(v,M-1);//tt是v的子树的结点数
for(int j=M;j>=1;--j){//枚举当前子树中选择总的结点个数
for(int k=1;k<=tt&&k<j;++k){//枚举从子树v中选择的总的结点个数
dp[u][j]=max(dp[u][j],dp[v][k]+dp[u][j-k]);
}
}
t+=tt;
}
return t;
}
int main()
{
ios::sync_with_stdio(false);
int N,M;
while(cin>>N>>M,N+M)
{
M++;//增加根节点
for(int i=0;i<=N;++i)
G[i].clear();
memset(dp,0,sizeof(dp));
value[0]=0;
int u;
for(int i=1;i<=N;++i){
cin>>u>>value[i];
G[u].push_back(i);
}
dfs(0,M);
cout<<dp[0][M]<<endl;
}
return 0;
}