2020 ICPC 南京站 M Monster Hunter (树形DP)

题目链接:M-Monster Hunter_第 45 届国际大学生程序设计竞赛(ICPC)亚洲区域赛(南京)

时间限制:C/C++ 1秒,其他语言2秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld

题目描述

There is a rooted tree with n vertices and the root vertex is 1. In each vertex, there is a monster. The hit points of the monster in the i-th vertex is hp_i.

Kotori would like to kill all the monsters. The monster in the i-th vertex could be killed if the monster in the direct parent of the i-th vertex has been killed. The power needed to kill the i-th monster is the sum of hp_i and the hit points of all other living monsters who lives in a vertex j whose direct parent is i. Formally, the power equals to



In addition, Kotori can use some magic spells. If she uses one magic spell, she can kill any monster using 0 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 m magic spells.

输入描述:

There are multiple test cases. The first line of input contains an integer T indicating the number of test cases. For each test case:

The first line contains an integer n (), indicating the number of vertices.

The second line contains (n-1) integers (), where p_i means the direct parent of vertex i.

The third line contains n integers () indicating the hit points of each monster.

It's guaranteed that the sum of n of all test cases will not exceed .

输出描述:

For each test case output one line containing  integers  separated by a space, where a_m indicates the minimum total power needed to kill all the monsters if Kotori can use m magic spells.

Please, DO NOT output extra spaces at the end of each line, otherwise your answer may be considered incorrect!
示例1

输入

复制 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 9
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 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 0
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 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;
}

参考资料

大佬的AC代码
2020年ICPC南京区域赛题解 - 知乎

  • 8
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值