2020 ICPC 南京站 M Monster Hunter (樹形DP)
阿新 • • 發佈:2020-12-23
題目連結:M-Monster Hunter_第 45 屆國際大學生程式設計競賽(ICPC)亞洲區域賽(南京)
時間限制:C/C++ 1秒,其他語言2秒空間限制:C/C++ 262144K,其他語言524288K
64bit IO Format: %lld
題目描述
There is a rooted tree with vertices and the root vertex is . In each vertex, there is a monster. The hit points of the monster in the -th vertex is .Kotori would like to kill all the monsters. The monster in the
In addition, Kotori can use some magic spells. If she uses one magic spell, she can kill any monster using
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 integerindicating 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.示例1
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 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;
}