题意:
有$n$种物品各有无限个,背包空间为$m$,第$i$种物品的体积为$a_i$,价值为$b_i$。求在不超过背包容量的前提下,取得的最大价值。
$nle 10^6,quad mle 10^{16},quad a_i,b_ile 100$且均为正整数。
分析一:
贪心一:
实际上最多有$100 imes 100=10^4$种物品,可以考虑去重,这样$n$的规模下降至$10^4$。
贪心二:
如果两种物品的体积相同,我们肯定不会装价值更低的那种物品,这样$n$的规模下降至$100$。
转化法:
由于$m$过于巨大,然而$a_i$相比较小,我们可以想到,性价比最优的物品一定会出现极多次,这样最终方案肯定可以分割成很多完全相同的小方案(全部装同一种物品),再在剩下的容量里精打细算。因此我们先做小容量(最大可支持的范围)的背包,再来组成最终方案。
综上,我们先做$nle 100$,$mle 2 imes 10^5$的完全背包,设$f_i$表示背包容量为$i(1le i le 2 imes 10^5)$时能取得的最大价值,则有$$f_m=mathop{max}limits_{i=1}^{2 imes 10^5}{lfloor m/i floor imes f_i + f_{m\%i}}$$
实现一(100分):

#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #define IL inline using namespace std; typedef long long LL; const int N=100; const int M=2e5; int n; LL m,w[103]; LL f[M+3]; int main(){ freopen("backpack.in","r",stdin); freopen("backpack.out","w",stdout); scanf("%d%lld",&n,&m); memset(w,0,sizeof w); for(int i=1;i<=n;i++){ LL x,y; scanf("%lld%lld",&x,&y); w[x]=max(w[x],y); } memset(f,0,sizeof f); for(int i=1;i<=N;i++) for(int j=i;j<=M;j++) f[j]=max(f[j],f[j-i]+w[i]); LL ans=0; for(int i=1;i<=M;i++) ans=max(ans,m/i*f[i]+f[m%i]); printf("%lld",ans); return 0; }
分析二:
前两个贪心策略同分析一。
贪心三:
我们已经知道,背包容量巨大,物品体积较小,性价比高的某一种物品一定会大量出现在最终方案中,现在我们想要确定它确切的出现次数。分析一还是偷了个懒,并没有做这件事情,只是由这个性质让答案自己“脱颖而出”,效率可不算高。接下来给出一种方法来确定“零”“整”的“分界线”。
设在性价比最高的物品中$a$最小的为$s$号,且最终方案中取了$x$种非$s$号物品,则一定有$$x<a_s ag{1}$$
先给出一个引理:
给定任意$n$个整数,它们之中存在若干个整数的和为$n$的倍数。
证明很简单,对这$n$个整数做前缀和,设为${S_n}$,那么对于$S_0,\,S_1\,S_2dots\,S_n$这$n+1$个整数,至少有两个数模$n$同余,它们的差就是$n$的倍数。
回到$(1)$,考虑反证法:若$xge a_s$,那么一定存在若干物品的价值之和是$a_s$的倍数,用$s$号物品替换它们一定不劣。
由此可知,非$s$号物品的总体积一定不会超过$100 imes a_s$。所以$s$号物品至少有$lfloor m/a_s floor -100$个。
那么做法就是先取一下$s$号物品,剩下的空间做完全背包。
有一点要注意:在找性价比最高的物品时,必须初始化为实际物品,不能用虚拟物品初始化,具体请看代码注释。
实现二(100分):

#include<iostream> #include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #define IL inline using namespace std; typedef long long LL; const int N=100; const int M=1e6; int n; LL m,w[103]; LL f[M+3]; IL bool eql(LL w1,LL v1,LL w2,LL v2){ return v1*w2==v2*w1; } IL bool betr(LL w1,LL v1,LL w2,LL v2){ return v1*w2>v2*w1; } int main(){ freopen("backpack.in","r",stdin); freopen("backpack.out","w",stdout); scanf("%d%lld",&n,&m); memset(w,0,sizeof w); for(int i=1;i<=n;i++){ LL x,y; scanf("%lld%lld",&x,&y); w[x]=max(w[x],y); } LL tw=1,tv=w[1];/////////////WA 50pts: LL tw=101,tv=0; for(LL i=2;i<=N;i++) if(betr(i,w[i],tw,tv)||(eql(i,w[i],tw,tv)&&i<tw)){ tw=i; tv=w[i]; } LL cnt=m/tw-100; m-=cnt*tw; LL tmp=tv*cnt; memset(f,0,sizeof f); for(LL i=1;i<=N;i++) for(LL j=i;j<=m;j++) f[j]=max(f[j],f[j-i]+w[i]); printf("%lld",tmp+f[m]); return 0; }
小结:
数据规模不同,造就不同题目。应学会灵活处理。