【纪中模拟2019.08.18】【JZOJ6309】完全背包

题目链接

题意:

  有$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;

}
View Code

 

分析二:

  前两个贪心策略同分析一。

  贪心三:

  我们已经知道,背包容量巨大,物品体积较小,性价比高的某一种物品一定会大量出现在最终方案中,现在我们想要确定它确切的出现次数。分析一还是偷了个懒,并没有做这件事情,只是由这个性质让答案自己“脱颖而出”,效率可不算高。接下来给出一种方法来确定“零”“整”的“分界线”。

  设在性价比最高的物品中$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;

}
View Code

 

小结:

  数据规模不同,造就不同题目。应学会灵活处理。

原文地址:https://www.cnblogs.com/Hansue/p/11374000.html