问题描述
有N种物品, 每种物品价值为 vi , 花费为 ci , 每种物品可以购买多个,现在有M单位的钱,要求能够买到的物品价值最大值。
解法
类似01背包,这里只是改变了物品可以购买多个,如果还按01背包的解法,dp(i, x)表示用x钱买前i种物品,可以得到的最大值,状态转移公式为:
dp(i,x)=max(dp(i−1,x−k∗ci)+k∗vi)|0≤k≤x/ci
k=0 表示不买第i种物品,k>0表示买k件第i种物品。
可以发现这样求解时存在重复的,
很容易得到下面的公式:
dp(i,x−ci)+vi=max(dp(i−1,x−ci−k∗ci)+k∗vi)+vi
0≤k≤(x−ci)/ci
=>
dp(i,x−ci)+vi=max(dp(i−1,x−ci−k∗ci)+k∗vi+vi)
0≤k≤(x−ci)/ci
=>
dp(i,x−ci)+vi=max(dp(i−1,x−(k+1)∗ci)+(k+1)∗vi)
0≤k≤(x−ci)/ci
dp(i,x−ci)+vi=max(dp(i−1,x−k′∗ci)+k′∗vi)
1≤k′≤x/ci
因此:
dp(i,x)=max(dp(i−1,x),dp(i,x−ci)+vi)
01背包时对于dp(i, 0) ~dp(i, M) 我们是从后往前求的,
现在只需要换过来从前往后求就可以了。
代码:
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
enum {maxn = 100000+5};
int dp[maxn];
#define OJ
int main()
{
#ifndef OJ
freopen("in.txt", "r", stdin);
#endif // OJ
int n, m;
scanf("%d %d", &n, &m);
memset(dp, 0, sizeof(int)*(m+1));
int need, value;
while(n--)
{
scanf("%d %d", &need, &value);
for (int i=need; i<= m; i++)
dp[i] = max(dp[i], dp[i-need]+ value);
}
printf("%d\n", dp[m]);
return 0;
}