【BZOJ 1017】 [JSOI2008]魔兽地图DotR

1017: [JSOI2008]魔兽地图DotR

Time Limit: 30 Sec   Memory Limit: 162 MB
Submit: 1069   Solved: 433
[ Submit][ Status]

Description

DotR (Defense of the Robots) Allstars是一个风靡全球的魔兽地图,他的规则简单与同样流行的地图DotA (Defense of the Ancients) Allstars。DotR里面的英雄只有一个属性——力量。他们需要购买装备来提升自己的力量值,每件装备都可以使佩戴它的英雄的力量值提高固定的点数,所以英雄的力量值等于它购买的所有装备的力量值之和。装备分为基本装备和高级装备两种。基本装备可以直接从商店里面用金币购买,而高级装备需要用基本装备或者较低级的高级装备来合成,合成不需要附加的金币。装备的合成路线可以用一棵树来表示。比如,Sange and Yasha的合成需要Sange, Yasha和Sange and Yasha Recipe Scroll三样物品。其中Sange又要用Ogre Axe, Belt of Giant Strength 和 Sange Recipe Scroll合成。每件基本装备都有数量限制,这限制了你不能无限制地合成某些性价比很高的装备。现在,英雄Spectre有M个金币,他想用这些钱购买装备使自己的力量值尽量高。你能帮帮他吗?他会教你魔法Haunt(幽灵附体)作为回报的。

Input

输入文件第一行包含两个整数,N (1 <= n <= 51) 和 m (0 <= m <= 2,000)。分别表示装备的种类数和金币数。装备用1到N的整数编号。接下来的N行,按照装备1到装备n的顺序,每行描述一种装备。每一行的第一个正整数表示这个装备贡献的力量值。接下来的非空字符表示这种装备是基本装备还是高级装备,A表示高级装备,B表示基本装备。如果是基本装备,紧接着的两个正整数分别表示它的单价(单位为金币)和数量限制(不超过100)。如果是高级装备,后面紧跟着一个正整数C,表示这个高级装备需要C种低级装备。后面的2C个数,依次描述某个低级装备的种类和需要的个数。

Output

第一行包含一个整数S,表示最多可以提升多少点力量值。

Sample Input

10 59
5 A 3 6 1 9 2 10 1
1 B 5 3
1 B 4 3
1 B 2 3
8 A 3 2 1 3 1 7 1
1 B 5 3
5 B 3 3
15 A 3 1 1 5 1 4 1
1 B 3 5
1 B 4 3

Sample Output

33


比较麻烦的树形dp。


注意题目中说了:合成的过程一定是满足一棵树的形状的!


dp方程:

f[i][j][k]表示第i个装备花k的钱,贡献j个给上级的最大力量,g[tot][j]表示当前是i的第tot个儿子,一共花费j的钱的最大力

量。


在这两个方程之上,我们需要嵌套一层循环all,表示第i个装备要做多少个。


那么我们先求g数组(要满足能做all个i):

g[tot][j]=max(g[tot][j],g[tot-1][j-k]+f[y][all*num[tot]][k])  

(k是花多少的钱做当前的儿子,y是当前儿子的编号,num[tot]表示做1个i用几个tot)


在当前的all下继续求f数组:

f[i][j][k]=max(f[i][j][k],g[tot][k]+p[i]*(all-j))  


注意:

now要从大到小循环,保证g数组是单增的,就可以避免每次清空g数组了。


#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#define inf 0x3f3f3f3f
using namespace std;
int in[60],h[60],tot=0,n,m,g[60][2005],l[60],p[60],c[60],f[60][106][2005];
struct edge
{
	int y,ne,num;
}e[200005];
void Addedge(int x,int y,int v)
{
	tot++;
	e[tot].y=y;
	e[tot].ne=h[x];
	h[x]=tot;
	e[tot].num=v;
	in[y]++;
}
void dp(int x)
{
	if (!h[x])
	{
		l[x]=min(l[x],m/c[x]);
		for (int i=0;i<=l[x];i++)
			for (int j=i;j<=l[x];j++)
				f[x][i][j*c[x]]=p[x]*(j-i);
		return;
	}
	l[x]=m;
	for (int i=h[x];i;i=e[i].ne)
	{
		int y=e[i].y;
		dp(y);
		l[x]=min(l[x],l[y]/e[i].num);
		c[x]+=e[i].num*c[y];
	}
	l[x]=min(l[x],m/c[x]);
	for (int i=1;i<=n;i++)
		for (int j=0;j<=m;j++)
			g[i][j]=-inf;
	g[0][0]=0;
	for (int all=l[x];all>=0;all--)
	{
		int tot=0;
		for (int i=h[x];i;i=e[i].ne)
		{
			tot++;
			int y=e[i].y;
			for (int j=0;j<=m;j++)
				for (int k=0;k<=j;k++)
					g[tot][j]=max(g[tot][j],g[tot-1][j-k]+f[y][all*e[i].num][k]);
		}
		for (int i=0;i<=all;i++)
			for (int k=0;k<=m;k++)
				f[x][i][k]=max(f[x][i][k],g[tot][k]+p[x]*(all-i));
	}
}
int main()
{
        scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
		for (int j=0;j<=105;j++)
			for (int k=0;k<=m;k++)
				f[i][j][k]=-inf;
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&p[i]);
		char s[5];
		scanf("%s",s);
		if (s[0]=='B') scanf("%d%d",&c[i],&l[i]);
		else
		{
			int k;
			scanf("%d",&k);
			while(k--)
			{
				int y,v;
				scanf("%d%d",&y,&v);
				Addedge(i,y,v);
			}
		}
	}
	int ans=0;
	for (int i=1;i<=n;i++)
		if (!in[i])
		{
			dp(i);
			for (int j=0;j<=m;j++)
				for (int k=0;k<=l[i];k++)
					ans=max(ans,f[i][k][j]);
			break;
		}
	cout<<ans<<endl;
	return 0;
}




感悟:

1.这道题f[i][j][k]这种转移方式中j表示给上级几个,这种转移方式还是第一次见!


2.这道题vfk有二维解法,以后看看

你可能感兴趣的:(dp,OI,bzoj)