0)01 背包,完全背包, 多重背包,混用。
解题关键有两个。
一个是将小倩付钱和老板找零分别做一个背包,小倩多重背包,老板无限背包,这样做可以的原因是,两个背包面值相同时对应的状态总可以作为一个不变的桥梁。
另一个是,处理小倩的多重背包时,采用二进制优化可以显著缩短时间,不然因为数据过多容易超时(哪怕代码有错算不出正确数据,只要超时了,就会返回超时)。
二进制优化的思想,就是利用任何数都可以被2^k(1、2、4、8...组合得到)表示,所以每个面值搜索1到n遍(n是该面值拥有数),可以降低成lgn次,得到新的一组面值(注意不是正好的2^k时,比如10 == 1 2 4 + 3,最后的3不要忘记),每个面值只有一张,再进行一次01背包即可。
啊,对了,还有题目最后,给出这样一句话:
But Xiaoqian is a low-pitched girl , she wouldn’t like giving out more than 20000 once.
告诉我们上限是20000,然而至今没有读懂它的意思,这不是告诉我们价换次数不超过20000次吗?那和费用有什么关系...
1) 二进制优化代码(31ms AC)
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
const int maxn=110;
const int maxm=20010;
const int INF=0x3f3f3f3f;
struct Stuff{
int v;
int c;
}stuff[maxn];
int bag_one[maxm];
int bag_two[maxm];
int main()
{
int n,t;
int kase=0;
while(scanf("%d%d",&n,&t)){
if(n==0){
break;
}
kase++;
for(int i=1;i<=n;i++){
scanf("%d",&stuff[i].v);
}
for(int i=1;i<=n;i++){
scanf("%d",&stuff[i].c);
}
for(int i=1;i<=maxm;i++){
bag_one[i]=INF;
bag_two[i]=INF;
}
//memset(bag_one,INF,sizeof(bag_one));//现在是31ms,如果将memset的两个初始化,替代上面的一个for,将是46ms
//memset(bag_two,INF,sizeof(bag_two));
bag_one[0]=0;
bag_two[0]=0;
//小倩付钱时,如果更改stuff,那么就把找零钱的for循环放在小倩付钱的for前面
//找零时,每种硬币个数不限,是完全背包
for(int i=1;i<=n;i++){
for(int j=stuff[i].v;j<=20000;j++){
bag_two[j]=min(bag_two[j],bag_two[j-stuff[i].v]+1);//找零j时,找零的最小个数
}
}
for(int i=1;i<=n;i++){
//如果数量足够多,就相当于完全背包
if(stuff[i].c*stuff[i].v>=20000){
for(int j=stuff[i].v;j<=20000;j++){
bag_one[j]=min(bag_one[j],bag_one[j-stuff[i].v]+1);
}
continue;
}
for(int k=1;k<=stuff[i].c;k*=2){//k是可以==stuff[i].c的
for(int j=20000;j>=stuff[i].v*k;j--){
bag_one[j]=min(bag_one[j],bag_one[j-stuff[i].v*k]+k);
}
stuff[i].c-=k;//!!!!!注意位置
}
for(int j=20000;j>=stuff[i].v*stuff[i].c;j--){
bag_one[j]=min(bag_one[j],bag_one[j-stuff[i].v*stuff[i].c]+stuff[i].c);
}
}
int minn=INF;
for(int j=t;j<=20000;j++){
minn=min(minn,bag_one[j]+bag_two[j-t]);
}
int flag=-1;
if(minn>=INF){
printf("Case %d: %d\n",kase,flag);
}
else{
printf("Case %d: %d\n",kase,minn);
//cout<<coux<<endl;
}
}
return 0;
}
2)二进制优化 //与1)不同在于将所有面值处理一遍得到的所有新的面值(每个面值只有一张),存到新的数组,然后再使用,156ms,AC
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
const int maxn=130;
const int maxm=23000;
const int INF=0x3f3f3f3f;
struct Stuff{
int v;
int c;
}stuff[maxn];
int bag_one[maxm];
int bag_two[maxm];
int main()
{
int n,t;
int kase=0;
while(scanf("%d%d",&n,&t)){
if(n==0){
break;
}
kase++;
for(int i=1;i<=n;i++){
scanf("%d",&stuff[i].v);
}
for(int i=1;i<=n;i++){
scanf("%d",&stuff[i].c);
}
memset(bag_one,INF,sizeof(bag_one));
memset(bag_two,INF,sizeof(bag_two));
bag_one[0]=0;
bag_two[0]=0;
Stuff stuff_new[maxm];
int cur=1;
for(int i=1;i<=n;i++){
int j;
for(j=1;j<=stuff[i].c;j*=2){
stuff_new[cur].v=j*stuff[i].v;
stuff_new[cur].c=j;
cur++;
}
if(j>stuff[i].c){
stuff_new[cur].v=(stuff[i].c-j/2)*stuff[cur].v;
stuff_new[cur].c=stuff[i].c-j/2;
cur++;
}
}
for(int i=1;i<cur;i++){
for(int j=20000;j>=stuff_new[i].v;j--){
bag_one[j]=min(bag_one[j],bag_one[j-stuff_new[i].v]+stuff_new[i].c);//fuqian
}
}
for(int i=1;i<=n;i++){
for(int j=stuff[i].v;j<=20000;j++){
bag_two[j]=min(bag_two[j],bag_two[j-stuff[i].v]+1);//找零j时,找零的最小个数
}
}
int minn=INF;
for(int j=t;j<=20000;j++){
if(minn>=bag_one[j]+bag_two[j-t]){
minn=min(minn,bag_one[j]+bag_two[j-t]);
}
}
int flag=-1;
if(minn>=INF){
printf("Case %d: %d\n",kase,flag);
}
else{
printf("Case %d: %d\n",kase,minn);
//cout<<coux<<endl;
}
}
return 0;
}
3)朴素算法,没有二进制优化,上限是蒙对了,正好在时限内AC,811ms
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
const int maxn=130;
const int maxm=23000;
const int INF=0x3f3f3f3f;
struct Stuff{
int v;
int c;
}stuff[maxn];
int bag_one[maxm];
int bag_two[maxm];
int main()
{
int n,t;
int kase=0;
while(scanf("%d%d",&n,&t)){
if(n==0){
break;
}
kase++;
int sum=0;
for(int i=1;i<=n;i++){
scanf("%d",&stuff[i].v);
sum+=stuff[i].v;//所有面值加一遍构成的上限,事后反证得,这样做并没有科学依据..蒙对数据的上限了
}
for(int i=1;i<=n;i++){
scanf("%d",&stuff[i].c);
}
memset(bag_one,INF,sizeof(bag_one));
memset(bag_two,INF,sizeof(bag_two));
bag_one[0]=0;
bag_two[0]=0;
for(int i=1;i<=n;i++){
if((stuff[i].c*stuff[i].v)>t+sum){
for(int j=stuff[i].v;j<=t+sum;j++){
bag_one[j]=min(bag_one[j],bag_one[j-stuff[i].v]+1);//付钱j时,付的最少个数
}
}
else{
for(int j=t+sum;j>=stuff[i].v;j--){//这里是倒序
for(int k=1;k<=stuff[i].c&&k*stuff[i].v<=j;k++){
bag_one[j]=min(bag_one[j],bag_one[j-k*stuff[i].v]+k);//付钱j时,付的最少个数
}
}
}
}
for(int i=1;i<=n;i++){
for(int j=stuff[i].v;j<=t+sum;j++){
bag_two[j]=min(bag_two[j],bag_two[j-stuff[i].v]+1);//找零j时,找零的最小个数
}
}
int minn=INF;
for(int j=t;j<=t+sum;j++){
if(minn>=bag_one[j]+bag_two[j]){
minn=min(minn,bag_one[j]+bag_two[j-t]);
}
}
int flag=-1;
if(minn>=INF){
printf("Case %d: %d\n",kase,flag);
}
else{
printf("Case %d: %d\n",kase,minn);
}
}
return 0;
}
4)
Description
And now , Xiaoqian wants to buy T (1 ≤ T ≤ 10,000) cents of supplies. The currency system has N (1 ≤ N ≤ 100) different coins, with values V1, V2, ..., VN (1 ≤ Vi ≤ 120). Xiaoqian is carrying C1 coins of value V1, C2 coins of value V2, ...., and CN coins of value VN (0 ≤ Ci ≤ 10,000). The shopkeeper has an unlimited supply of all the coins, and always makes change in the most efficient manner .But Xiaoqian is a low-pitched girl , she wouldn’t like giving out more than 20000 once.
Input
Line 1: Two space-separated integers: N and T.
Line 2: N space-separated integers, respectively V1, V2, ..., VN coins (V1, ...VN)
Line 3: N space-separated integers, respectively C1, C2, ..., CN
The end of the input is a double 0.
Output
Sample Input
3 70 5 25 50 5 2 1 0 0
Sample Output
Case 1: 3