其实这题是我从noip前就开始做的。。。那个时候打的Pascal,一直TLE,转了C++之后我又写了一遍,A了。。。
辛酸史:
题目描述:
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(幽灵附体)作为回报的。
数据范围:物品数(n)<=51,金币数(m)<=2000,限购数量(limit)<=100。
思路分析:
可以说是一道树上背包DP吧。
观察题目,注意到:限购数量是题目的关键,对于一个物品,它合成几件,或是把几件用于父亲装备的合成,是题目恶心的地方,既然这样的话我们就把它设计到状态里面。然后再看看数据范围,限购小于等于100——很明显了,出题人就是想让我们把限购数量放进状态里面。再和普通的背包结合一下,得到状态,f[u][i][j]表示:对于以u为根的子树,在以u为根的子树上,我们投入i个金币,并且把j个物品u用于父亲装备的合成,以u为根的子树能产生的最大力量值。
然后我们还需要一个g数组来辅助f数组的转移。g[i][j]表示:对于u的前i个儿子装备,在以它们为根的子树中投入j个金币,能得到的最大力量值。
转移时我们需要枚举的很重要的一环就是:制作几个u装备,从而让整个转移能够顺利地进行下去,我们把它设为l。
得到g数组的转移:g[i][j]=max(g[i][j],g[i-1][j-k]+f[v][k][l*need]) 其中v为u的第i个儿子,而need则为制作一个装备u需要几个装备v,k为在以v为根的子树中花费k个金币。
通过g数组的转移,继而得到f数组的转移:f[u][i][j]=max(f[u][i][j],g[siz[u]][i]+w[u]*(l-j)) 其中siz[u]表示u有几个儿子。
最后求答案的时候我们又需要一个dp数组来求解。(嗯,没错,一道题目,三个背包dp,我也不得不佩服出题人了——你是真TM的毒瘤!)
最后这个就是最简单的背包dp了(如果这个都想不到的话,建议你前往xx省xx市实验学校,和某张姓老师学习一段时间)
状态dp[i][j],表示前i个“终极装备”(终极装备就是无法用于合成的装备),花费j个金币,能得到的最大力量值。转移的时候枚举一下在第i个装备上花费多少金币就可以了,具体不懂的话还是看代码吧,太简单了,也不想多讲了。
代码实现:
#include<bits/stdc++.h> using namespace std; const int inf=1000000000; int f[55][2005][105],g[55][2005],dp[55][2005]; int a[55][55],need[55][55],limit[55],cost[55],w[55],siz[55]; bool root[55],son[55]; int n,m; void dfs(int u){ if (!son[u]){ limit[u]=min(limit[u],m/cost[u]); for (int i=0;i<=limit[u];i++) for (int j=0;j<=i;j++) f[u][i*cost[u]][j]=w[u]*(i-j); return; } limit[u]=inf; for (int i=1;i<=siz[u];i++){ dfs(a[u][i]); cost[u]+=cost[a[u][i]]*need[u][i]; limit[u]=min(limit[u],limit[a[u][i]]/need[u][i]); } limit[u]=min(limit[u],m/cost[u]); memset(g,-0x3f,sizeof(g)); g[0][0]=0; for (int l=limit[u];l>=0;l--){ //这里倒着枚举l,是为了避免多次给g数组赋初值 for (int i=1;i<=siz[u];i++) for (int j=0;j<=m;j++) for (int k=0;k<=j;k++) g[i][j]=max(g[i][j],g[i-1][j-k]+f[a[u][i]][k][l*need[u][i]]); for (int i=0;i<=l;i++) for (int j=0;j<=m;j++) f[u][j][i]=max(f[u][j][i],g[siz[u]][j]+w[u]*(l-i)); } } int main(){ memset(f,-0x3f,sizeof(f)); scanf("%d%d",&n,&m); int nn,ans=0; for (int i=1;i<=n;i++){ scanf("%d",&w[i]); char ch[3]; scanf("%s",&ch); if (ch[0]=='A'){ scanf("%d",&nn); for (int j=1;j<=nn;j++) scanf("%d%d",&a[i][j],&need[i][j]),root[a[i][j]]=true; son[i]=true; siz[i]=nn; } else scanf("%d%d",&cost[i],&limit[i]); } int now=0; for (int i=1;i<=n;i++) if (!root[i]){ dfs(i); now++; for (int j=0;j<=m;j++) for (int k=0;k<=j;k++) dp[now][j]=max(dp[now][j],dp[now-1][j-k]+f[i][k][0]); } for (int i=0;i<=m;i++) ans=max(ans,dp[now][i]); printf("%d ",ans); return 0; }