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
思路:首先自己做的时候可以按照题目要求构造出森林,对于那些没有父节点的点,我们用一个类似虚拟源点的点作为根节点把森林变成图。
然后我就开始dfs,发现很难满足条件dfs出来。好了是没学过的树形dp了,补题。
参考了几篇博客,借鉴了网上的思路。
这题是树形dp,不过这个dp的类型是个01背包。
首先这个题类似金明的方案是个依赖性背包,可以转化到01背包或者分组背包去做。
这题我们转化到01背包去做。
01背包是按前i组选不选第i组里面的任意个物品来看的
结合一下
类推过来:
定义dp[i][j]:以i为根节点的子树选了j个节点的最大值
之前做dp都是从前往后推,用前面的状态更新后面的状态。
那么树形dp先dfs到叶节点,然后从下往上更新上面的状态。
直接来看:
dp[i][j]=max(dp[i1][j1]+dp[i2][j2]+….+dp[ik][jk])+a[i]
其中j1+j2+…+jk=j-1 这个1是留个i这个子树的根节点的
其中i1,i2,i3…ik是i的儿子节点
代表的含义是:
i为根节点选了j个节点的子树最大值
来自选了i这个点的价值+剩下与i直连的儿子节点并且以这些儿子节点为子树的分别选了一定条数的总和的最大值
i的度数就是有多少组。
j就是背包的体积,当前最多能选多少点
k表示第i个组的物品选k个。
注意分组背包转移的时候是一个物品的价值,这里转移的是dp的和,是多个,是01背包
不好想的话就想象三层的二叉树,然后去想一下这个过程。
#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<map>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=400;
typedef long long LL;
LL value[maxn];
vector<LL>tree[maxn];
LL dp[maxn][maxn];
LL n,m;
void dfs(LL u,LL w){
dp[u][1]=value[u];
for(LL i=0;i<tree[u].size();i++){
LL to=tree[u][i];
dfs(to,w-1);
for(LL j=w;j>1;j--){//给父亲节点留一个
for(LL k=0;k<j;k++){
dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[to][k]);
}
}
}
}
int main(void)
{
cin.tie(0);std::ios::sync_with_stdio(false);
while(cin>>n>>m&&n!=0&&m!=0){
memset(dp,-0x3f,sizeof(dp));
memset(value,0,sizeof(value));
for(LL i=0;i<=n;i++) tree[i].clear();
for(LL i=1;i<=n;i++){
LL a,b;cin>>a>>b;
value[i]=b;
tree[a].push_back(i);
}
m++;//多建一个虚拟源点构建树
dfs(0,m);
cout<<dp[0][m]<<endl;
}
return 0;
}