题意:现在玩游戏欲升级,升级需要经验值n,杀怪可以赚经验值,但是会扣忍耐度,游戏中有k种怪,数目都无限多。现在玩家还有m点忍耐度,问能否在最多杀s个怪的情况下升级,若能则输出剩余的最大忍耐度。
思路:
1、此题有两个约束,一个是忍耐度,一个是最多杀的怪物数。抽象来说意味着对于每件物品,具有两种不同的费用。此即为二维背包的模型!而二维费用背包模型最常见的形式便是:物品总个数有上限限制,如此题的最多杀s个怪。而关于二维背包的处理,无非是增加一个状态维度即可,转移方程类比着选或不选第i个物品的思路写出即可。
2、此题怪物数目无限多,也即这是个完全背包问题。关于完全背包问题,存在O(N*V)时间复杂度的算法(N为物品总数,V为背包容量), 实现方式即为
f[i,j]=max(f[i-1,j],f[i,j-v[i]]+w[i]) 其中循环变量i遍历物品种类,j正向遍历0~V
注意递推式与01背包有些许不同:
(1)max的第二项是f[i,...],是i!
(2)j在01背包时是逆序循环的,如今变成了顺序循环。
这样的目的是,该子结果就是已经考虑过重复选第i项的结果了!另外这个i和j如果需要是可以交换循环顺序的。
当然依然可以空间优化,去除物品种类那一维。而值得注意的是此题需要算的是如果可以升级需输出能剩余的最大忍耐度,故循环时可以将忍耐度那一维放在最外层循环,找到满足条件的break输出即可,尔后是怪物种类和怪物个数放在里层循环,代码如下:
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
int w[105],v[105];//w为经验(价值),v为忍耐度(容量1,而杀怪总数为容量2)
int dp[105][105]; //空间优化后的 忍耐度最多为v时,杀怪数为u时的最大经验值
int main()
{
int n,m,k,s;
while(~scanf("%d%d%d%d",&n,&m,&k,&s))
{
for(int i=0;i<k;i++) scanf("%d%d",&w[i],&v[i]);
memset(dp,0,sizeof(dp));
int flag=1;
for(int vv=0;vv<=m;vv++) //此题是完全背包,故正向循环;且与i交换了循环位置
{
for(int i=0;i<k;i++)
for(int u=1;u<=s;u++) //此题是完全背包,故正向循环
if(vv>=v[i]) dp[vv][u]=max(dp[vv][u],dp[vv-v[i]][u-1]+w[i]);
if(dp[vv][s]>=n)
{
printf("%d\n",m-vv);
flag=0;
break;
}
}
if(flag) printf("-1\n");
}
return 0;
}