【DP计划】11.7——[CF]CF815C(树形依赖背包)MEDIUM

题目传送门
附图:

在这里插入图片描述

题目大意:
你要去商店买东西,每个东西有一个价格 c i c_i ci和一个折扣 d i d_i di,你可以享受到物品 d i d_i di的折扣当且仅当你之前买了物品 x i x_i xi,并且享受到了物品 x i x_i xi的折扣。保证除第一个点之外,每个点都只有一个 x i x_i xi(第一个点没有 x i x_i xi)。你有 m m m元钱,求你最多能买多少东西。

由题意得,折扣关系是一棵树,享受这个点的折扣的条件是买它的父亲,并且父亲也享受折扣,因此也要买父亲的父亲,父亲的父亲也享受折扣……因此,享受一个点折扣最终的条件就是这个点的祖先全部都买。
这样也就形成了一个依赖关系,我们定义数组 f [ i ] [ j ] f[i][j] f[i][j]表示点 i i i,它的子树(包括自己)买了 j j j个点,且购买点 i i i时享受到了折扣的最小费用; g [ i ] [ j ] g[i][j] g[i][j]表示点 i i i,它的子树里买了 j j j个点,并且所有的点都不享受折扣的最小费用。那么进行背包处理,即可得出转移的方程式:
g[x][0]=0;f[x][1]=v[x]-dc[x];g[x][1]=v[x];siz[x]=1;
for(i=siz[x];i>=0;i--)
  for(j=1;j<=siz[to];j++)
    g[x][i+j]=min(g[x][i+j],g[x][i]+g[to][j]);
for(i=siz[x];i>=1;i--)
  for(j=1;j<=siz[to];j++)
    f[x][i+j]=min(f[x][i+j],f[x][i]+min(f[to][j],g[to][j])); 
这个方程式可以这样理解,对于上面那个 g g g数组,相当于就是一个分组背包,每个子树可以看成一个组,组里只能选一种情况。
f f f数组也是同理,只是由于 i i i点必须享受到折扣,所以 i i i点必选,那么枚举 s i z e size size的时候只枚举到 1 1 1即可。
这里还有一个操作,就是 s i z [ x ] siz[x] siz[x]先处理后加,这样做能极大地优化时间复杂度。
#include<bits/stdc++.h>
#define MAXN 5005
#define ll long long
using namespace std;
ll read(){
	char c;ll x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
	while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
ll n,m,ans,flag,v[MAXN],dc[MAXN],f[MAXN][MAXN],g[MAXN][MAXN];
ll cnt,head[MAXN<<1],nxt[MAXN<<1],go[MAXN<<1],siz[MAXN];
void add(ll x,ll y){
	go[cnt]=y;nxt[cnt]=head[x];head[x]=cnt;cnt++;
}
void dfs(ll x){
	register int i,j,k;
	g[x][0]=0;f[x][1]=v[x]-dc[x];g[x][1]=v[x];siz[x]=1;
	for(k=head[x];k!=-1;k=nxt[k]){
		ll to=go[k];dfs(to);
		for(i=siz[x];i>=0;i--)
		 for(j=1;j<=siz[to];j++)
		  g[x][i+j]=min(g[x][i+j],g[x][i]+g[to][j]);
		for(i=siz[x];i>=1;i--)
		 for(j=1;j<=siz[to];j++)
		  f[x][i+j]=min(f[x][i+j],f[x][i]+min(f[to][j],g[to][j])); 
		siz[x]+=siz[to];
	}
}
int main()
{
	n=read();m=read();register int i;
	memset(head,-1,sizeof(head));
	memset(f,127,sizeof(f));
	memset(g,127,sizeof(g));
	for(i=1;i<=n;i++){
		v[i]=read();dc[i]=read();
		if(i>1){ll x=read();add(x,i);}
	}
	dfs(1);
	for(i=n;i;i--)
	 if(f[1][i]<=m||g[1][i]<=m){flag=1;printf("%d",i);break;}
	if(!flag) puts("0");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值