Gym102292M.Monster Hunter(树形背包+滚动数组)
题意:
给出一棵n个点的树。
只有把一个点的父节点们全部消灭了才能消灭这个点。
消灭一个点的代价是它本身的生命值和它子节点的生命值之和。
同时你也可以用魔法0代价消灭任意一个点。
询问使用k次魔法,消灭所有点的最小代价。输出k=0到n的答案。
题解:
定义\(f(i,j,k)\)表示第\(i\)个点的子树内有\(j\)个节点被魔法消灭时,第\(i\)个节点自己是否用魔法消灭所能得到的最小代价。
这样就可以对每个点的叶子做树形背包。
但是一开始怎么搞都弄不过样例。
看了别人的博客后才发现,如果直接这样定义状态会导致一些状态的复用,需要再加一维x表示仅考虑当前节点的前x个儿子。
这样就是\(f(i,j,k,x)\)表示第i个点的子树内有j个节点被魔法消灭时,第i个节点自己是否用魔法消灭所得到的最小代价,仅考虑前x个儿子。
可以把x这一维滚动掉,注意滚动数组每一轮要清空。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2005;
const long long inf=1e13;
typedef long long ll;
int n,a[maxn];
vector<int> g[maxn];
ll f[maxn][maxn][2][2];//第i个点的子树内用魔法删除j个点,同时第i个点本身是否用魔法删除,当前考虑前k个叶子(滚动数组)
int size[maxn];
int e[maxn];
void dfs (int x) {
size[x]=1;
int k=0;
f[x][0][0][k]=a[x];
f[x][1][1][k]=0;
for (int y:g[x]) {
dfs(y);
for (int i=0;i<=size[x]+size[y];i++) f[x][i][0][k^1]=f[x][i][1][k^1]=inf;
for (int i=0;i<=size[x];i++) {
for (int j=0;j<=size[y];j++) {
f[x][i+j][0][k^1]=min(f[x][i+j][0][k^1],f[x][i][0][k]+min(f[y][j][0][e[y]]+a[y],f[y][j][1][e[y]]));
f[x][i+j][1][k^1]=min(f[x][i+j][1][k^1],f[x][i][1][k]+min(f[y][j][0][e[y]],f[y][j][1][e[y]]));
}
}
k^=1;
size[x]+=size[y];
}
e[x]=k;
}
int main () {
int _;
scanf("%d",&_);
while (_--) {
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*2;j++) for (int k=0;k<2;k++) for (int x=0;x<2;x++) f[i][j][k][x]=inf;
for (int i=2;i<=n;i++) {
int x;
scanf("%d",&x);
g[x].push_back(i);
}
for (int i=1;i<=n;i++) scanf("%d",a+i);
dfs(1);
for (int i=0;i<=n;i++) {
if (i) printf(" ");
printf("%lld",min(f[1][i][0][e[1]],f[1][i][1][e[1]]));
}
printf("\n");
}
}