给出一棵有 n n n个结点的树,每棵结点 i i i有一个血量为 h p [ i ] hp[i] hp[i]的怪物,需要花费他与距离他为1的所有地方的存活的怪物的血量总和的能力来击败他。现在有个人从1结点开始进攻怪物,直到所有怪物都死掉为止。他会吟唱一种魔法,每次吟唱会直接消灭任意一个地方的怪物,问使用 k k k次的情况下,需要的起始能量最低是多少,才足以支持他杀完所有怪物?求出 k = 0 , 1 , 2 , 3 , . . . , n k=0,1,2,3,...,n k=0,1,2,3,...,n的所有结果
因为目前点的花费与儿子删除与否存在关系,所以多开一维保存点是否删除,设 d p [ u ] [ i ] [ 0 / 1 ] dp[u][i][0/1] dp[u][i][0/1]为子树 u u u,在子树内用 i i i次魔法,是否删去 u u u的最少初始值是多少,那么只有当 u u u和他的儿子 v v v都没被删除时,才会需要初始值更大,于是有转移:
d p [ u ] [ i ] [ 0 ] = m i n ( d p [ u ] [ j − k ] [ 0 ] + m i n ( d p [ v ] [ k ] [ 0 ] + h p [ v ] , d p [ v ] [ k ] [ 1 ] ) ) dp[u][i][0]=min(dp[u][j-k][0]+min(dp[v][k][0]+hp[v],dp[v][k][1])) dp[u][i][0]=min(dp[u][j−k][0]+min(dp[v][k][0]+hp[v],dp[v][k][1]))
d p [ u ] [ i ] [ 1 ] = m i n ( d p [ u ] [ j − k ] [ 1 ] + m i n ( d p [ v ] [ k ] [ 0 ] , d p [ v ] [ k ] [ 1 ] ) ) dp[u][i][1]=min(dp[u][j-k][1]+min(dp[v][k][0],dp[v][k][1])) dp[u][i][1]=min(dp[u][j−k][1]+min(dp[v][k][0],dp[v][k][1]))
此时是必须选择一个合并的,而不能舍弃某棵子树,于是 d p [ u ] [ i ] [ 0 / 1 ] dp[u][i][0/1] dp[u][i][0/1]是不能和自己取最小的,一般用两个数组来更新,初始化 t m p tmp tmp为极大值,然后计算答案在 t m p tmp tmp里计算,最后写入 d p dp dp数组,转移方程用 t m p tmp tmp的形式如下
t m p [ u ] [ i ] [ 0 ] = m i n ( t m p [ u ] [ i ] [ 0 ] , d p [ u ] [ j − k ] [ 0 ] + m i n ( d p [ v ] [ k ] [ 0 ] + h p [ v ] , d p [ v ] [ k ] [ 1 ] ) ) tmp[u][i][0]=min(tmp[u][i][0],dp[u][j-k][0]+min(dp[v][k][0]+hp[v],dp[v][k][1])) tmp[u][i][0]=min(tmp[u][i][0],dp[u][j−k][0]+min(dp[v][k][0]+hp[v],dp[v][k][1]))
t m p [ u ] [ i ] [ 1 ] = m i n ( t m p [ u ] [ i ] [ 1 ] , d p [ u ] [ j − k ] [ 1 ] + m i n ( d p [ v ] [ k ] [ 0 ] , d p [ v ] [ k ] [ 1 ] ) ) tmp[u][i][1]=min(tmp[u][i][1],dp[u][j-k][1]+min(dp[v][k][0],dp[v][k][1])) tmp[u][i][1]=min(tmp[u][i][1],dp[u][j−k][1]+min(dp[v][k][0],dp[v][k][1]))
并且一开始 d p dp dp数组不能设0,需要设一个极大值,而这个极大值被计算时还需要看作0,这是树形背包一个很毒瘤的点,因为如果设0就更新不了最小值,所以只能设极大值,而计算时又需要用真实值,细节请看代码
树形背包优化的点之前只注意了上界,设 j j j是被更新的点, k k k是枚举子树的背包大小,显然被更新的点不能大于现在的背包容量(设目前 s u m [ u ] sum[u] sum[u]已经合并了 s u m [ v ] sum[v] sum[v]),即 j ≤ s u m [ u ] j\leq sum[u] j≤sum[u]是上界限制,还有一个下界限制是一直没注意到的,这里会导致超时,看转移方程,一般都是 d p [ u ] [ j ] = d p [ u ] [ j − k ] + d p [ v ] [ k ] dp[u][j]=dp[u][j-k]+dp[v][k] dp[u][j]=dp[u][j−k]+dp[v][k]的形式,利用了之前做完的背包,和枚举子树大小来更新现在的背包,那么以前的背包大小只做到了 s u m [ u ] − s u m [ v ] sum[u]-sum[v] sum[u]−sum[v](设此时的 s u m [ u ] sum[u] sum[u]已经包括了 s u m [ v ] sum[v] sum[v]),于是 j − k ≤ s u m [ u ] − s u m [ v ] j-k\leq sum[u]-sum[v] j−k≤sum[u]−sum[v],即 k ≥ j − s u m [ u ] + s u m [ v ] k\geq j-sum[u]+sum[v] k≥j−sum[u]+sum[v],同理 k k k还需要不超过子树大小和 u u u目前的大小,即 k ≤ m i n ( s u m [ v ] , j ) k\leq min(sum[v],j) k≤min(sum[v],j)
之后还需要注意一些下标合法性的问题,这些问题都比较毒瘤
// #include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
using ll=long long;
const int N=2005,inf=0x3fffffff;
const long long INF=0x3f3f3f3f3f3f,mod=998244353;
struct way
{
int to,next;
}edge[N<<1];
int cnt,head[N];
void add(int u,int v)
{
edge[++cnt].to=v;
edge[cnt].next=head[u];
head[u]=cnt;
}
int n,hp[N],sum[N];
ll dp[N][N][2],tmp[N][2];//根是否被消灭了
void dfs(int u,int fa)
{
sum[u]=1;
dp[u][0][0]=hp[u];
dp[u][1][1]=0;
for(int i=head[u];i;i=edge[i].next)
{
int v=edge[i].to;
if(v==fa) continue;
dfs(v,u);
sum[u]+=sum[v];
for(int j=sum[u];j>=0;j--)
{
tmp[j][0]=tmp[j][1]=INF;
for(int k=max(0,j-sum[u]+sum[v]);k<=min(j,sum[v]);k++)
{
tmp[j][0]=min(tmp[j][0],(dp[u][j-k][0]==INF?0:dp[u][j-k][0])+dp[v][k][0]+hp[v]);
if(k>=1) tmp[j][0]=min(tmp[j][0],(dp[u][j-k][0]==INF?0:dp[u][j-k][0])+dp[v][k][1]);
if(j-k>=1) tmp[j][1]=min(tmp[j][1],(dp[u][j-k][1]==INF?0:dp[u][j-k][1])+dp[v][k][0]);
if(k>=1&&j-k>=1) tmp[j][1]=min(tmp[j][1],(dp[u][j-k][1]==INF?0:dp[u][j-k][1])+dp[v][k][1]);
}
}
for(int j=0;j<=sum[u];j++)
for(int k=0;k<=1;k++) dp[u][j][k]=tmp[j][k];
}
}
int main()
{
int t; scanf("%d",&t);
while(t--)
{
scanf("%d",&n); cnt=0;
for(int i=1;i<=n;i++) head[i]=0;
for(int i=2;i<=n;i++)
{
int x; scanf("%d",&x);
add(x,i); add(i,x);
}
for(int i=1;i<=n;i++)
{
scanf("%d",&hp[i]);
for(int j=0;j<=n;j++) dp[i][j][0]=dp[i][j][1]=INF;
}
//击杀i的怪物需要他自己的血量和周围怪物的血量和的能量
//求最少能量,使得用m次魔法就可以全部杀光
dfs(1,0);
printf("%lld ",dp[1][0][0]);
for(int i=1;i<=n;i++) printf("%lld ",min(dp[1][i][0],dp[1][i][1]));
putchar('\n');
}
return 0;
}