[2012山东ACM省赛] Pick apples (贪心,完全背包,枚举)
Pick apples
Time Limit: 1000MS Memory limit: 165536K
题目描述
Once ago, there is a mystery yard which only produces three kinds of apples. The number of each kind is infinite. A girl carrying a big bag comes into the yard. She is so surprised because she has never seen so many apples before. Each kind of apple has a size and a price to be sold. Now the little girl wants to gain more profits, but she does not know how. So she asks you for help, and tell she the most profits she can gain.
输入
In the first line there is an integer T (T <= 50), indicates the number of test cases.
In each case, there are four lines. In the first three lines, there are two integers S and P in each line, which indicates the size (1 <= S<= 100) and the price (1 <= P <= 10000) of this kind of apple.
In the fourth line there is an integer V,(1 <= V <= 100,000,000)indicates the volume of the girl‘s bag.
输出
For each case, first output the case number then follow the most profits she can gain.
示例输入
1 1
1 2
1 3
1 6
示例输出
Case 1: 6
提示
来源
解题思路:
从昨天赛完就开始弄这道题,当时做的时候第一感觉就是完全背包,但是数据量太大了,直接完全背包肯定会超时。赛后看解题报告才知道这题用的是大范围贪心,小范围完全背包,这个也好懂,看了网上AC的代码小范围用的是1000,但这个今天被老师证实是错误的,这题后台测试数据不完善,比如老师给的这组测试数据:
98 99
99 100
100 101
2000 这组测试数据用小范围为1000的代码测试结果为2000,但是正确答案应该是2020,虽然第一种是最优的东西,但是这里我们要全部选择第三种100 101的才是正确答案。所以小范围为1000是错误的,这里的小范围应该是三种物品容量的最小公倍数,即100*100*100,用1000000就可以了。做这个题花了真不少功夫,整个上午差不多就是在runtime error 和 wrong answer中度过的。找错找了很久。注意的是:限定的容量V要用long long类型,dp[]数组也要用 long long类型。dp[]数组不能只开1000000个,要尽量大点,因为在贪心部分只选择最优的那种物品,但是大于1000000的那部分不一定能整除该物品的容量,也就是说可能会剩余一部分容量加到1000000里面一起用完全背包来做。这个就是无数次runtime error的原因所在。后来又看到一种解法,特别巧妙,没有用到贪心和背包,就是枚举除了最优物品之外的两外两种背包的选择的个数,这里有一个限定条件,那就是不是最优物品的物品被选择的个数一定比最优物品的容量要小,
比如这组测试数据;
最优物品 3 6
其他 2 3 选一个 2 3 选两个 4 6 选三个6 9 但这时候我们选2个最优物品的情况是 6 12,显然比选三个其他物品要好,也就是说其他物品可能选的个数的情况有0 个,1个,2个。这些都是有可能的,本题背包的容量都小于100,所以两层循环枚举其他两种物品被选择的个数,在计算中取最大值就可以了,复杂度为0(100*100)。
第一种方法(排序做的贪心+完全背包):
#include <iostream> #include <string.h> #include <algorithm> using namespace std; const int lcm=1000000;//三种类size的最小公倍数 long long dp[lcm*2+2],V;//dp数组要开的足够大,不能只开lcm个,原因见后面注释,还有V要用long long struct N { int s,p; double pri; }node[3]; bool cmp(N a ,N b) { return a.pri<b.pri; } long long maxll(long long a,long long b) { return a>b?a:b; } void compack()//完全背包 { memset(dp,0,sizeof(dp)); for(int i=0;i<3;i++) for(int j=node[i].s;j<=V;j++) dp[j]=maxll(dp[j],dp[j-node[i].s]+node[i].p); } int main() { int t;cin>>t;int c=1; while(t--) { for(int i=0;i<3;i++) { cin>>node[i].s>>node[i].p; node[i].pri=node[i].s*1.0/node[i].p; } cin>>V; if(V<=lcm)//小范围直接完全背包 { compack(); cout<<"Case "<<c++<<": "<<dp[V]<<endl; } else { sort(node,node+3,cmp);//排序,最优的在第一个 V-=lcm;//超出1000000的部分 long long ans=0; ans=ans+(V/node[0].s*node[0].p);//贪心部分选择第一个所获得的价值 V=lcm+(V-V/node[0].s*node[0].s);//这里解释了为什么dp要开的足够大,不能只开lcm个,因为在贪心部分不一定能整除,有余下的部分 compack();//完全背包 cout<<"Case "<<c++<<": "<<ans+dp[V]<<endl; } } return 0; }
第二种方法(未排序的贪心+完全背包):
#include <iostream> #include <string.h> using namespace std; const int lcm=1000000; long long dp[lcm*2+2],V; int s[5],p[5]; void compack() { memset(dp,0,sizeof(dp)); for(int i=0;i<3;i++) for(int j=s[i];j<=V;j++) { if(dp[j]<dp[j-s[i]]+p[i]) dp[j]=dp[j-s[i]]+p[i]; } } int main() { int t;cin>>t;int c=1; while(t--) { double pr,temp=0; int id; for(int i=0;i<3;i++) { cin>>s[i]>>p[i]; pr=1.0*p[i]/s[i]; if(temp<pr) { temp=pr; id=i; } } cin>>V; if(V<=lcm) { compack(); cout<<"Case "<<c++<<": "<<dp[V]<<endl; } else { long long ans=(V-lcm)/s[id]*p[id]; V=V-(V-lcm)/s[id]*s[id]; compack(); cout<<"Case "<<c++<<": "<<ans+dp[V]<<endl; } } }
第三种方法(枚举未排序):
#include <iostream> #include <stdio.h> using namespace std; int s[4],p[4]; long long llmax(long long a,long long b) { return a>b?a:b; } int main() { int t; cin>>t; int c=1; while(t--) { for(int i=1; i<=3; i++) cin>>s[i]>>p[i]; int V; cin>>V; int k1=1,k2,k3; for(int i=2; i<=3; i++) { if(p[i]*s[k1]>p[k1]*s[i])//判断优先级 k1=i; } if(k1==1){k2=2;k3=3;};//k1是最优的物品,k2,k3是谁没有关系 if(k1==2){k2=1,k3=3;}; if(k1==3){k2=1,k3=2;}; long long ans=0; for(int i=0; i<s[k1]; i++)//枚举 { for(int j=0; j<s[k1]; j++) { long long temp=i*s[k2]+j*s[k3]; if(temp>V) break; else { long long v=V-temp; ans=llmax(ans,v/s[k1]*p[k1]+i*p[k2]+j*p[k3]);//选最大值 } } } cout<<"Case "<<c++<<": "<<ans<<endl; } return 0; }
第四种方法(排序枚举):
#include <iostream> #include <stdio.h> #include <algorithm> using namespace std; struct N { int s,p; double pri; }node[4]; bool cmp(N a,N b) { if(a.pri<b.pri) return true; return false; } long long llmax(long long a,long long b) { return a>b?a:b; } int main() { int t; cin>>t; int c=1; while(t--) { for(int i=0; i<3; i++) { cin>>node[i].s>>node[i].p; node[i].pri=1.0*node[i].s/(1.0*node[i].p); } int V; cin>>V; sort(node,node+3,cmp); long long ans=0; for(int i=0; i<node[0].s; i++) { for(int j=0; j<node[0].s; j++) { long long temp=i*node[1].s+j*node[2].s; if(temp>V) break; else { long long v=V-temp; ans=llmax(ans,v/node[0].s*node[0].p+i*node[1].p+j*node[2].p); } } } cout<<"Case "<<c++<<": "<<ans<<endl; } return 0; }