ZCMU-1381-砝码(母函数、多重背包)

1381: 简单题

Time Limit: 1 Sec   Memory Limit: 128 MB
Submit: 133   Solved: 55
[ Submit][ Status][ Web Board]

Description

这里有不同重量的砝码 可以是1g,2g。。。现在给你一个数N表示有N种重量的砝码  ai。。。an表示重量  bi。。。bn表示数量  问你不能称量出最少几克的重量  (最大不超过8500克哦亲)

Input

第一行输入N表示砝码的重量种类(N=0结束)

接下来N行每行输入ai ,bi表示砝码的质量和数量(a<100,b<100)

Output

输出不能称量出的质量中最少的质量

Sample Input

3
1 1
2 1
5 3
3
1 1
2 1
3 1

Sample Output

4
7
【解析】
鉴于这道题,我想说一下母函数和多重背包的概念,当然我也只是刚刚了解,讲的可能不太清楚。其实在砝码称重当中
给你砝码的重量和砝码的数量,叫你求,你能称出多少质量,你能称出多少方案。这个时候我举个百度百科里面的两个
例子给大家感受一下。

有1克、2克、3克、4克的砝码各一 枚,能称出哪几种重量?每种重量各有几种可能方案?
考虑用母函数来解决这个问题:
我们假设x表示砝码,x的指数表示砝码的重量,这样:
1个1克的砝码可以用函数1+x表示,
1个2克的砝码可以用函数1+x∧2表示,
1个3克的砝码可以用函数1+x∧3表示,
1个4克的砝码可以用函数1+x∧4表示。
就比如我们拿1+x^2来说,这里1代表的就是取重量为2的砝码为0时的情况。而x^2这里的x代表的是砝码他的指数代表的
是几克的砝码,这是是2所以代表的是2克的砝码。然后呢它前面的系数代表有几个这样的砝码。把组合问题的加法法则
和幂级数的t的乘幂的相加对应起来。这句话还是蛮重要的。这个时候我们就可以开始求了。
(1+x)(1+x∧2)(1+x∧3)(1+x∧4)
= (1+x+x^2+x^3)(1+x^3+x^4+x^7)
=1+x+x^2+2x^3+2x^4+2x^5+2x^6+2x^7+x^8+x^9+x^10
从上面的函数知道:可称出从1克到10克,系数便是方案数。
例如右端有2x项,即称出5克的方案有2:5=3+2=4+1;同样,6=1+2+3=4+2;10=1+2+3+4。
故称出6克的方案有2,称出10克的方案有1 。我们可以先让前面(1+x)先和(1+x^2)相乘先,看它们两组合能组合出来
什么砝码的重量,然后再不断的和后面相乘。
对于我们这道题我们需要有一个数组来记录之前两个多项式相乘所得到的那些个指数当中的系数以此来知道到底有多
少个方案。我们拿样例一来举例子的话就是(1+x)(1+x^2)*(1+x^5+x^10+x^15)为什么5克的砝码有三个的时候我们要这
么写?因为两个5克的砝码可以凑成是10克,三个5克的砝码可以凑成是15克。还有一点需要注意的是,两个数相乘指数
是相加的。所以就相当于是能凑出多少克的砝码了。这样便于理解。这道题其实就是通过这样子的。还是拿样例一来说
首先先做的是列出式子(1+x)(1+x^2)*(1+x^5+x^10+x^15)在这里我们先算(1+x)*(1+x^2)把这里面的有的指数的系数都记
录下来。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
#include<string>
#define INF 0x7fffffff
using namespace std;
int f1[9000],f2[9000];
int a[100],b[100];//a数组记录砝码的质量,b数组记录砝码的数量
int main(){
    int n;
    while(~scanf("%d",&n)&&n){
        memset(f1,0,sizeof(f1));
        memset(f2,0,sizeof(f2));
        int sum=0,i,j,k;
        for(i=0;i<n;i++) scanf("%d%d",&a[i],&b[i]);
        for(i=0;i<=b[0];i++){
            f1[a[0]*i]=1;
        }
        sum+=a[0]*b[0];
        for(i=1;i<n;i++){
            for(j=0;j<=sum;j++){
                for(k=0;k<=b[i];k++){
                    f2[j+k*a[i]]+=f1[j];;//比如前面指数为3,系数为3,
                    //即f1[3]=3 的一项和下一个表达式的指数为k*a[i]的相乘
                    //如果这个时候k*a[i]等于3
                    //则得到的式子的系数为,f2[j+k*a[i]=f2[6]+=f1[3]
                    //又a[1]=3,所以指数为4的系数为b[6]=3;
                }
            }
            sum+=a[i]*b[i];
            for(j=0;j<=sum;j++){
                f1[j]=f2[j];//记录前面的多项式相乘的结果
                f2[j]=0;
            }
        }
        for(i=1;i<=sum;i++){
            if(f1[i]==0){
                printf("%d\n",i);
                break;
            }
        }
        if(i>sum) printf("%d\n",sum+1);//表示前面都能组成比如1,2,2的砝码分别只有一个
        //1,2,3,4,5都能组成所以6不能组成
    }
    return 0;
}


这道题还可以用多重背包来解释,多重背包的话我现在举个例子来解释一下,其实我们可以把它分成若干个01背包
问题 多重背包转换成 01 背包问题就是多了个初始化,把它的件数C 用二进制分解成若干个件数的集合,这里面
数字可以组合成任意小于等于C的件数,而且不会重复,之所以叫二进制分解,是因为这样分解可以用数字的二进制
形式来解释比如说7,我们可以转换成 001 010 100。用这三个数可以组成任意一个小于等于7的数。所以我们就可
以把它分解开。下方代码转载自http://blog.csdn.net/lyhvoyage/article/details/8545852 详情请看此博文。
int n;  //输入有多少种物品
int c;  //每种物品有多少件
int v;  //每种物品的价值
int s;  //每种物品的尺寸
int count = 0; //分解后可得到多少种物品
int value[MAX]; //用来保存分解后的物品价值
int size[MAX];  //用来保存分解后物品体积

scanf("%d", &n);    //先输入有多少种物品,接下来对每种物品进行分解

while (n--)     //接下来输入n中这个物品
{
    scanf("%d%d%d", &c, &s, &v);  //输入每种物品的数目和价值
    for (int k=1; k<=c; k<<=1)   //<<右移 相当于乘二
    {
        value[count] = k*v;
        size[count++] = k*s;
        c -= k;
    }
    if (c > 0)
    {
        value[count] = c*v;
        size[count++] = c*s;
    }
}
转换成01背包问题的
#include <iostream>
using namespace std;
int main()
{
    int nCase,Limit,nKind,i,j,k,  v[111],w[111],c[111],dp[111];
    //v[]存价值,w[]存尺寸,c[]存件数
    //在本题中,价值是米的重量,尺寸是米的价格
    int count,Value[1111],size[1111];
    //count存储分解完后的物品总数
    //Value存储分解完后每件物品的价值
    //size存储分解完后每件物品的尺寸
    cin>>nCase;
    while(nCase--)
    {
        count=0;
        cin>>Limit>>nKind;
        for(i=0; i<nKind; i++)
        {
            cin>>w[i]>>v[i]>>c[i];
            //对该种类的c[i]件物品进行二进制分解
            for(j=1; j<=c[i]; j<<=1)
            {
                //<<左移1位,相当于乘2
                Value[count]=j*v[i];
                size[count++]=j*w[i];
                c[i]-=j;
            }
            if(c[i]>0)
            {
                Value[count]=c[i]*v[i];
                size[count++]=c[i]*w[i];
            }
        }
        //经过上面对每一种物品的分解,
        //现在Value[]存的就是分解后的物品价值
        //size[]存的就是分解后的物品尺寸
        //count就相当于原来的n
        //下面就直接用01背包算法来解
        memset(dp,0,sizeof(dp));
        for(i=0; i<count; i++)
            for(j=Limit; j>=size[i]; j--)
                if(dp[j]<dp[j-size[i]]+Value[i])
                    dp[j]=dp[j-size[i]]+Value[i];

        cout<<dp[Limit]<<endl;
    }
    return 0;
}
此处就要讲一下多重背包应用到这道题的用法了。其实套路还是一样的。就是看能不能让这个重量的砝码给装满那
我们也可以进行分解。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
#include<string>
#define INF 0x7fffffff
using namespace std;
int f[9000];
int a[100],b[100];
void zeroone(int v,int w){
    for(int i=9000;i>=w;i--){
        if(f[i]<f[i-w]+v) //此处其实就是转换成求01背包的问题去求最大价值是多少
            f[i]=f[i-w]+v;
    }
}
int main(){
    int n,k;
    while(~scanf("%d",&n)&&n){
        memset(f,0,sizeof(f));
        for(int i=0;i<n;i++) scanf("%d%d",&a[i],&b[i]);
        for(int i=0;i<n;i++){
            for(k=1;k<b[i];){
                zeroone(a[i]*k,a[i]*k);//它的重量和它的价值是一样的
                b[i]-=k;//数量减少了
                k<<=1;
            }
            zeroone(a[i]*b[i],a[i]*b[i]);
        }
        for(int i=1; ;i++){
            if(f[i]!=i){//判断有没有装满的其实就是判断最少那个重量不能称
                printf("%d\n",i);
                break;
            }
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值