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");
	}
}
 
posted @ 2021-03-31 20:00  zlc0405  阅读(80)  评论(0编辑  收藏  举报