2020南京icpc M 树形背包

题意:

给出一棵有 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的所有结果

Solution:

因为目前点的花费与儿子删除与否存在关系,所以多开一维保存点是否删除,设 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][jk][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][jk][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][jk][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][jk][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] jsum[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][jk]+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] jksum[u]sum[v],即 k ≥ j − s u m [ u ] + s u m [ v ] k\geq j-sum[u]+sum[v] kjsum[u]+sum[v],同理 k k k还需要不超过子树大小和 u u u目前的大小,即 k ≤ m i n ( s u m [ v ] , j ) k\leq min(sum[v],j) kmin(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;
}

你可能感兴趣的:(动态规划)