【题目描述】
为了庆贺班级在校运动会上取得全校第一名成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动员。期望拨款金额能购买最大价值的奖品,可以补充他们的精力和体力。
【输入】
第一行二个数n(n≤500),m(m≤6000),其中n代表希望购买的奖品的种数,m表示拨款金额。
接下来n行,每行3个数,v、w、s,分别表示第I种奖品的价格、价值(价格与价值是不同的概念)和能购买的最大数量(买0件到s件均可),其中v≤100,w≤1000,s≤10。
【输出】
一行:一个数,表示此次购买能获得的最大的价值(注意!不是价格)。
【输入样例】
5 1000 80 20 4 40 50 9 30 50 7 40 30 6 20 20 1
【输出样例】
1040
思路:其实这个题完全可以改一下完全背包的代码,只要把循环里的条件改成几件就可以,但是那样的话复杂度是O(n^3),有点慢,所以改进一下代码。
这个拆分的思路非常巧妙(也有可能我是第一次听所以感觉巧妙),把一个数拆成2的n次方和一个别的数,比如说33,33=1+32=1+2+30=1+2+4+26=1+2+4+8+18=1+2+4+8+16+2,把这个数拆开,然后把其中的任意几个数相加,会发现,这些数的和可以取到1~33中的任意值,这样就可以大大简化时间复杂度,先上代码
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<bits/stdc++.h>
using namespace std;
int f[6500],v[510],w[510],k[510],v_[6500],w_[6500];//数组大小开到n*log2(m)就可以
int n,m,cnt;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>v[i]>>w[i]>>k[i];//输入
for(int i=1;i<=n;i++)
{
int r=1;//2的0次方开始
while(r<=k[i])//如果可以再拆,那么继续
{
cnt++;//cnt这个变量的含义其实可以理解为种类,比如说把两个第一类物品捆绑到一起,就变成了一种物品,因为这种物品可以通过和其他的加来得到任意一个数,所以这样做是没问题的
v_[cnt]=v[i]*r;//新数组,就是捆绑之后的数据
w_[cnt]=w[i]*r;
k[i]-=r;//别忘了-r
r=r*2;//2的下一次方
}
if(k[i]!=0)//如果此时还有剩余,那么再开一个,存进去
{
cnt++;
v_[cnt]=v[i]*k[i];
w_[cnt]=w[i]*k[i];
k[i]=0;
}
}
for(int i=1;i<=cnt;i++)//循环,从第一种开始循环
for(int j=m;j>=v_[i];j--)//其实下面就是01背包的操作
f[j]=max(f[j],f[j-v_[i]]+w_[i]);
cout<<f[m];
return 0;
}