查了一些博客都说是裸背包,对01背包理解不够好,想了很久才懂。
这个类比一下就很好理解了。
对于经典的01背包:
dp[i][j] 代表:对于前 i 件物品,放入重量为 j 的物品的最大价值
转移方程:
i from 1 to n
j from 0 to W
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + val[i] )
这个很好理解,类比这道题
dp[i][j][k] 代表:在第 i 个节点,对于其前 j 个子树,有 k - 1 次进攻机会能得到的最大价值 (有一次要攻击自己)
转移方程:
j from 1 to J
k from 2 to M
t from 1 to k - 1 // t 代表分配给子树 j的攻击次数
dp[i][j][k] = max( dp[i][j-1][k], dp[i][j-1][k-t] + dp[ v(j) ][j-1][t] ) (i的第j个子节点)
01背包有一个空间复杂度的优化,将 重量的遍历顺序颠倒,即
i from 1 to n
j from W to 0
dp[j] = max( dp[j], dp[j-w[i]]+val[i] )
同理这个题也可以进行这个优化
j from 1 to J
k from M to 2
t from 1 to k - 1
dp[i][k] = max(dp[i][k], dp[i][k-t] + dp[v(j)][t] )
另外要注意 M要 M++ 因为多攻击一次根节点
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int N,M;
const int MAXE = 500;
const int SIZE = 300;
struct node
{
int to,next;
}p[MAXE];
int val[SIZE];
int head[SIZE];
int dp[SIZE][SIZE];
int no = 0;
void init()
{
memset(head,-1,sizeof(head));
memset(dp[0],0,sizeof(dp[0])); // dp[0] 初始化
no = 0;
}
void add(int a,int b)
{
p[no].next = head[a];
p[no].to = b;
head[a] = no;
no++;
}
void dfs(int u)
{
for(int i=head[u];i!=-1;i=p[i].next)
{
int v = p[i].to;
dfs(v);
for(int j=M;j>1;j--)
for(int k=1;k<j;k++)
dp[u][j] = max(dp[u][j], dp[v][j-k]+dp[u][k]);
}
}
int main()
{
while(scanf("%d%d",&N,&M)!=EOF && N)
{
init();
M++;//important 多一次攻击总结点
for(int i=1;i<=N;i++)
{
int a,b;
scanf("%d%d",&a,&b);
add(a,i);
//val[i] = b;
for(int j=1;j<=M;j++) dp[i][j] = b;
// 每个j值都要初始化,中间省略了[0]这一状态
//现在的dp[i][j]的意义为 对于前0个子节点有j-1次进攻机会能拿到的最大价值
}
dfs(0);
printf("%d\n",dp[0][M]);
}
return 0;
}