题目链接:M-Monster Hunter_第 45 届国际大学生程序设计竞赛(ICPC)亚洲区域赛(南京)
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
题目描述
In addition, Kotori can use some magic spells. If she uses one magic spell, she can kill any monster using power without any restriction. That is, she can choose a monster even if the monster in the direct parent is alive.
For each , Kotori would like to know, respectively, the minimum total power needed to kill all the monsters if she can use magic spells.
输入描述:
There are multiple test cases. The first line of input contains an integer indicating the number of test cases. For each test case:
The first line contains an integer (), indicating the number of vertices.
The second line contains integers (), where means the direct parent of vertex .
The third line contains integers () indicating the hit points of each monster.
It's guaranteed that the sum of of all test cases will not exceed .
输出描述:
For each test case output one line containing integers separated by a space, where indicates the minimum total power needed to kill all the monsters if Kotori can use magic spells.
Please, DO NOT output extra spaces at the end of each line, otherwise your answer may be considered incorrect!
输入
复制 3 5 1 2 3 4 1 2 3 4 5 9 1 2 3 4 3 4 6 6 8 4 9 4 4 5 2 4 1 12 1 2 2 4 5 3 4 3 8 10 11 9 1 3 5 10 10 7 3 7 9 4 93 5 1 2 3 4 1 2 3 4 5 9 1 2 3 4 3 4 6 6 8 4 9 4 4 5 2 4 1 12 1 2 2 4 5 3 4 3 8 10 11 9 1 3 5 10 10 7 3 7 9 4 9
输出
复制 29 16 9 4 1 0 74 47 35 25 15 11 7 3 1 0 145 115 93 73 55 42 32 22 14 8 4 1 029 16 9 4 1 0 74 47 35 25 15 11 7 3 1 0 145 115 93 73 55 42 32 22 14 8 4 1 0
题目大意
给你一颗树,树上每个节点都是一个 h p i hp_i hpi血量的怪物。打败每个怪物所需要的能量值为 h p i + 所 有 存 活 的 直 接 子 节 点 的 h p j hp_i+所有存活的直接子节点的hp_j hpi+所有存活的直接子节点的hpj。每次必须要消灭父节点后才能消灭子节点。此外你还有m个魔咒,每个魔咒可以不耗费能量且可以消灭任意一个存活的怪物。问你m=0,1,2,3…,n时的最低总能量花费分别为多少。
分析
树、节点权重、最低总花费,看到这些东西就想到了树上DP。
首先要思考消灭每个怪物所需的能量仅与它本身和存活的子节点的HP之和有关,这就让人联想到了部分树上DP中的“遍历有效子节点个数”的思想。所以我们假设状态 f ( u , c n t ) f(u,cnt) f(u,cnt)为节点u有cnt个直接子节点存活时的最小能量开销。又考虑到节点u自身也有存活和不存活和不存货两种状态,所以额外添加一个bool类型的存活标志,则状态变为 f ( a l i v e , u , c n t ) f(alive,u,cnt) f(alive,u,cnt)。
此时可以列出转移方程:
f[0][u][i] <- f[0][u][i-j]+f[0][v][j];
f[0][u][i] <- f[0][u][i-j]+f[1][v][j];
f[1][u][i] <- f[1][u][i-j]+f[0][v][j];
f[1][u][i] <- f[1][u][i-j]+f[1][v][j]+w[v];
// 子节点存活且父节点存活时,子节点自身的权重才对答案有贡献
起始状态为
f[0][u][0]=0;
f[1][u][1]=w[u];
其他点=1LL*1e18;
然后去实现,提交,结果超时了。
我做到这里时已经心灵崩坏了,单单推出DP方程就已经精神疲惫了。遂去学习大佬的代码,发现可以考虑对转移方程的方向做个转换得:
f[0][u][j+k]=min(f[0][u][j+k],f[0][u][j]+min(f[0][v][k],f[1][v][k]));
f[1][u][j+k]=min(f[1][u][j+k],f[1][u][j]+min(f[0][v][k],f[1][v][k]+w[v]));
然后在每次处理的子节点个数上做点小文章。时间复杂度就可以大幅下降了。
代码
#include <bits/stdc++.h>
#define debug 0
using namespace std;
typedef long long ll;
const int MAXN=2e3+5;
vector<int> g[MAXN];
ll f[2][MAXN][MAXN];
ll w[MAXN];
int siz[MAXN];
void dfs(int u){
f[0][u][0]=0;
f[1][u][1]=w[u];
siz[u]=1;
for(int v:g[u]){
dfs(v);
for(int j=siz[u];j>=0;j--){
for(int k=siz[v];k>=0;k--){
f[0][u][j+k]=min(f[0][u][j+k],f[0][u][j]+min(f[0][v][k],f[1][v][k]));
f[1][u][j+k]=min(f[1][u][j+k],f[1][u][j]+min(f[0][v][k],f[1][v][k]+w[v]));
}
}
siz[u]+=siz[v];
}
}
void solve(){
int n;
scanf("%d",&n);
for(int i=0;i<=n;i++) g[i].clear();
for(int i=0;i<=n;i++){
for(int j=0;j<=n;j++){
f[1][i][j]=f[0][i][j]=1LL*1e18;
}
}
for(int i=2;i<=n;i++){
int u;
scanf("%d",&u);
g[u].push_back(i);
}
for(int i=1;i<=n;i++){
scanf("%lld",&w[i]);
}
dfs(1);
for(int i=n;i>=0;i--){
printf("%lld ",min(f[0][1][i],f[1][1][i]));
}
printf("\n");
}
int main(){
int T;
scanf("%d",&T);
while(T--){
solve();
}
return 0;
}