HDU 1561(树形DP)

来源:互联网 发布:苹果手机变音软件 编辑:程序博客网 时间:2024/05/20 06:30

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;}