题目地址:http://acm.hdu.edu.cn/showproblem.php?pid=1561
The more, The Better
Time Limit: 6000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 7644 Accepted Submission(s): 4492
Problem Description
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
Author
8600
Source
HDU 2006-12 Programming Contest
Recommend
LL | We have carefully selected several similar problems for you: 1011 2159 2639 1203 2602
思路:有依赖关系的树上dp,很经典的题目;网上解法一大堆!但是,我在刚看的时候有很多不懂, 网上的题解又多数只给出程序或者一条状态转移方程,个人看起来很吃力.所以,说一下自己本来没搞懂的梗!以便遇到相似状况的朋友理解.
显然,要选择子节点,则必须先选父亲节点.所以就有了状态转移方程:dp[u][j]=max(dp[u][j], dp[u][k] + dp[v][j-k]); dp[i][j]表示在i子树攻打j个城堡能获得珠宝最大值;
我的疑问是:dp[u][j]所取策略的顶点集合J是否包含dp[v][k]所取策略的顶点集合K?(这里j>k) . 答案是否定的,因为如果一颗树从左到右有n棵子树的话,那么当我考虑第i棵子树的时候, dp[u][k]事实上表示的是只考虑前i - 1棵子树的最优策略,因而J和K是不可能相交的.这样,当把n棵子树全部考虑完毕时,dp[i][j]就确确实实表示在i子树攻打j个城堡能获得珠宝最大值了. 另外,关于i,j的遍历顺序,看过背包问题的应该都可以理解.
还有一个比较有用的优化,就是j从min(m,num)(num:已经遍历过的节点个数)开始递减,减少了很多无谓的操作,效率高很多, 看其他人的题解都是直接从m开始,让我看的一头雾水…Orz
见代码:
//邻接表实现:
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
int const maxn=210;
typedef vector<int> vec;
vector<vec> G;
int n,m;
int data[maxn];//珠宝数
int dp[maxn][maxn];//第i个节点选j个课程
void init()
{
G.clear();
G.resize(n+1);
for (int i=0; i<=n; i++){
for (int j=1; j<=m; j++){
dp[i][j]=0;
}
}
}
int dfs(int u)
{
dp[u][1]=data[u];//只选1
int num=1;//根节点数
for (int i=0; i<(int)G[u].size(); i++){
int &v=G[u][i];
num+=dfs(v);//以遍历节点数
for (int j=min(num, m); j>0; j--){//j不可能大于min(num, m);
for (int k=1; k<j; k++){//u选k,剩下给v.
dp[u][j]=max(dp[u][j], dp[u][k] + dp[v][j-k]);
//这里的f[u][k]所代表的意义是:在遍历v这棵子树前f[u][k]的最优值.
//所以这里可以排除同一子树分支上f[u][k]和f[u][j]取值重复的bug,
//因为f[u][k]还没有考虑当前分支!!!(i递增,j递减)
}
}
}
return num;
}
int main()
{
while (scanf("%d%d",&n,&m)==2)
{
if (!n&&!m) break;
m++;//多选了0号点,切记无漏
init();
for (int i=1; i<=n; i++){
int a, b;
scanf("%d%d",&a,&b);
data[i]=b;
G[a].push_back(i);
}
dfs(0);
printf("%d\n",dp[0][m]);
}
return 0;
}
//向前星实现:
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
int const maxn=210;
int to[maxn],head[maxn],Next[maxn];
int n,m;
int data[maxn];//珠宝数
int dp[maxn][maxn];//第i个节点选j个课程
int tol;
void init()
{
for (int i=0; i<=n; i++){
for (int j=1; j<=m; j++){
dp[i][j]=0;
}
to[i]=Next[i]=head[i]=0;
}
tol=0;
}
void add(int a, int b)
{
to[++tol]=b;//默认0号边为非法边
Next[tol]=head[a];
head[a]=tol;
}
int dfs(int u)
{
dp[u][1]=data[u];//只选1
int num=1;//根节点数
for (int i=head[u]; i; i=Next[i]){
int &v=to[i];
num+=dfs(v);//以遍历节点数
for (int j=min(num, m); j>0; j--){
//j不可能大于min(num, m);优化比较明显,没有的话,时间15:220;
for (int k=1; k<j; k++){//u选k,剩下给v.
dp[u][j]=max(dp[u][j], dp[u][k] + dp[v][j-k]);
//这里的f[u][k]所代表的意义是:在遍历v这棵子树前f[u][k]的最优值.
//所以这里可以排除同一子树分支上f[u][k]和f[u][j]取值重复的bug,
//因为f[u][k]还没有考虑当前分支!!!(i递增,j递减)
}
}
}
return num;
}
int main()
{
while (scanf("%d%d",&n,&m)==2)
{
if (!n&&!m) break;
m++;//多选了0号点,切记无漏
init();
int a;
for (int i=1; i<=n; i++){
scanf("%d%d",&a,&data[i]);
add(a,i);
}
dfs(0);
printf("%d\n",dp[0][m]);
}
return 0;
}