cf 816E Karen and Supermarket

题目大意

给定\(n\)一颗树,每个点上有一个物品
每个物品有价格\(c[i]\)
有优惠券,能使价格减少\(d[i]\)
但是使用优惠券的前提时购买该物品,且父亲也使用优惠券
给定钱包余额\(lim\)
求最多能买多少物品
\(n\le 5000, c[i],d[i],lim\le 10^9\)

分析

树上背包
由于价值的数字很大,不能用钱来表示状态,个数表示dp值
只能先计算购买\(k\)个的最少价钱,再判断限制

\(f[x][i][0]\)表示\(x\)这个点不用优惠券,子树中买了\(i\)个物品的最低价钱
\(f[x][i][1]\)表示\(x\)这个点不用优惠券,子树中买了\(i\)个物品的最低价钱

使用子树不断合并到当前点的方法,可以使复杂度变为\(n^2\)
(每个点对在贡献一次\(O(1)\)复杂度后合并到一个状态中,相互不会再产生贡献)

做法

\(x\)为当前点,\(y\)为该点的儿子

边界条件
f[x][0][0]=0 ,f[x][0][1]=INF
f[x][1][0]=c[i], f[x][1][1]=c[i]-d[i]

合并转移(k=i+j)
f[x][k][0]=f[x][i][0]+f[y][j][0]
f[x][k][1]=f[x][i][1]+min(f[y][j][0],f[y][j][1])

实现时会算重(因为是01背包)
法1:枚举和\(k\),逆着扫\(k\),再枚举i或j中的一个
法2:枚举\(i\),逆着扫\(i\),再枚举\(j\)

solution

#include <cstdio>
#include <cstring>
#include <cctype>
#include <cmath>
#include <algorithm>
#include <cstdlib>
using namespace std;
const int M=5e3+7;
typedef long long LL;

inline int rd(){
	int x=0;bool f=1;char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
	for(;isdigit(c);c=getchar()) x=x*10+c-48;
	return f?x:-x;
}

struct vec{
	int g[M],te;
	struct edge{
		int y,nxt;
		edge(int _y=0,int _nxt=0){y=_y,nxt=_nxt;}
	}e[M<<1];
	vec(){memset(g,0,sizeof g);te=0;}
	inline void push(int x,int y){e[++te]=edge(y,g[x]);g[x]=te;}
	inline void push2(int x,int y){push(x,y);push(y,x);}
	inline int& operator () (int x){return g[x];}
	inline edge& operator [] (int x){return e[x];}
}e;

int n,sz[M];
LL lim,c[M],d[M];
LL f[M][M][2];

void dfs(int x,int fa){
	int i,j,k,p,y;
	sz[x]=1;
	f[x][0][0]=0;
	f[x][1][0]=c[x];
	f[x][1][1]=c[x]-d[x];

	for(p=e(x);p;p=e[p].nxt)
	if((y=e[p].y)!=fa){
		dfs(y,x);

		for(k=sz[x]+sz[y];k>=0;k--)
		for(j=0;j<=sz[y];j++) if((i=k-j)<=sz[x]){
			f[x][k][0]=min(f[x][k][0],f[x][i][0]+f[y][j][0]);
			f[x][k][1]=min(f[x][k][1],min(f[x][i][1]+f[y][j][0],f[x][i][1]+f[y][j][1]));
		}

		sz[x]+=sz[y];
	}
}

int main(){

	int i,x;
	n=rd(); lim=rd();

	for(i=1;i<=n;i++){
		c[i]=rd(), d[i]=rd();
		if(i>1) e.push(rd(),i);
	}

	memset(f,0x3f,sizeof f);
	dfs(1,0);

	int ans=0;
	for(i=0;i<=n;i++) if(min(f[1][i][0],f[1][i][1])<=lim) ans=i;
	printf("%d\n",ans);

	return 0;
}
posted @ 2017-07-09 15:11  _zwl  阅读(1254)  评论(0编辑  收藏  举报