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
Input
Output
Sample Input
3 70 5 25 50 5 2 1 0 0
Sample Output
Case 1: 3