1. 程式人生 > 其它 >2020 ICPC 南京站 M Monster Hunter (樹形DP)

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 dfs(int u){
	f[0][u][0]=0;
	f[1][u][1]=w[u];
	int cnt=1;
	int len=g[u].size();
	for(int v:g[u]){
		int child=dfs(v);
		for(int j=cnt;j>=0;j--){
			for(int k=child;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]));
			}
		}
		cnt+=child;
	}
	return cnt;
}

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南京區域賽題解 - 知乎