zoukankan      html  css  js  c++  java
  • [POI2005]BAN-Bank Notes

    题目描述:

      Byteotian Bit Bank (BBB) 拥有一套先进的货币系统,这个系统一共有n种面值的硬币,面值分别为b1, b2,…, bn. 但是每种硬币有数量限制,现在我们想要凑出面值k求最少要用多少个硬币.

      

      这一题其实很容易看出是一道背包问题,为什么我NOIP2018没有想到,此题应该属于多重背包问题,我们显然可以,将f[i]定为拼出i元需要的最少钞票数,将v定为花费的钞票数,将w定为这些钞票加起来的总价值,再利用多重背包的拆分方法。上链接已述,此处不多做叙述。

    但重点在于怎样记录:一开始我的想法是将每个f[i]都记录其转移的来pre[i],输出的时候向下不断temp=pre[temp]来输出。但这样是不行的

    错误代码如下:

    #include <algorithm>
    #include <iostream>
    #include <cmath>
    #include <cstring>
    #include <map>
    #include <string>
    #include <vector>
    #include <queue>
    #include <stack>
    #include <cstdio>
    #include <cstdlib>
    using namespace std;
    typedef long long ll;
    inline int read()
    {
        register int p(1),a(0);register char ch=getchar();
        while((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
        if(ch=='-') p=-1,ch=getchar();
        while(ch>='0'&&ch<='9') a=a*10+ch-48,ch=getchar();
        return a*p;
    }
    const int N=210,M=20010;
    int n,m,f[M],b[N],biao[M][2],zh[M][N],pre[M],hole[N],c[N],temp=1;
    int main()
    {
        n=read();
        for(int i=1;i<=n;i++) b[i]=read();
        for(int i=1;i<=n;i++) c[i]=read();
        m=read();
        memset(f,0x3f,sizeof(f));
        f[0]=0;
        for(int i=1;i<=n;i++)
        {
            temp=1;
            while((temp<<1)<=c[i]+1&&b[i]*temp<=m)
            {
                for(int j=m;j>=b[i]*temp;j--) if(f[j]>f[j-b[i]*temp]+temp)
                {
                    f[j]=f[j-b[i]*temp]+temp;
                    biao[j][0]=i,biao[j][1]=temp;
                    pre[j]=j-b[i]*temp;
                }
                temp<<=1;
            }
            if((temp<<1)>=c[i])
            {
                temp=c[i]-temp+1;
                for(int j=m;j>=b[i]*temp;j--) if(f[j]>f[j-b[i]*temp]+temp)
                {
                    f[j]=f[j-b[i]*temp]+temp;
                    biao[j][0]=i,biao[j][1]=temp;
                    pre[j]=j-b[i]*temp;
                }
            }
        }
        printf("%d
    ",f[m]);
        temp=m;
        while(temp)
        {
            printf("%d ",temp);
            hole[biao[temp][0]]+=biao[temp][1];
            temp=pre[temp];
        }
        for(int i=1;i<=n;i++)
            printf("%d ",hole[i]); 
        return 0;
    }

    这样做连样例都过不了,我们考虑是为什么。首先我们考虑01背包为什么要倒叙循环,因为这样就不会重复使用同一个物品。那么此算法的错误之处就出在这里。

    样例中要求拼出的数是10,有2 3 5三个数,其中5 只有一个,那么我们从f[10] ( f[m] ) 开始循环,我们首先取到5,于是我们接着我们循环f[5]这时我们就发现biao[5]在第循环5的时候被修改过了,所以它也用的是5这张钞票,但5的钞票只有一张,于是就出现错误了,显然一维无法解决这个问题了,必须用二维。此题卡空间,int[3000][20000],消耗约228MB,而此题空间上限为64MB,而如果我们使用bool类型就可以过,我们用cun[i][j],表示在f[i][j]处是否发生转移而下一个要循环的其实是可以算出来为f[i-1][j-w[i]],就这样知道j为0为止

    实现如下:

    #include <algorithm>
    #include <iostream>
    #include <cmath>
    #include <cstring>
    #include <map>
    #include <string>
    #include <vector>
    #include <queue>
    #include <stack>
    #include <cstdio>
    #include <cstdlib>
    using namespace std;
    typedef long long ll;
    inline int read()
    {
        register int p(1),a(0);register char ch=getchar();
        while((ch<'0'&&ch>'9')&&ch!='-') ch=getchar();
        if(ch=='-') p=-1,ch=getchar();
        while(ch>='0'&&ch<='9') a=a*10+ch-48,ch=getchar();
        return a*p;
    }
    const int N=210,M=20010,N2=3010;
    int n,m,f[M],b[N],v[N2],w[N2],bef[N2],hole[N],c[N],temp=1,cnt=0,ji=0;
    bool cun[N2][M];
    int main()
    {
        n=read();
        for(int i=1;i<=n;i++) b[i]=read();
        for(int i=1;i<=n;i++) c[i]=read();
        m=read();
        memset(f,0x3f,sizeof(f));
        f[0]=0;
        for(int i=1;i<=n;i++)
        {
            temp=1;
            while((temp<<1)<=c[i]+1&&b[i]*temp<=m)
            {
                v[++cnt]=temp;
                w[cnt]=b[i]*temp;
                bef[cnt]=i;
                temp<<=1;
            }
            if((temp<<1)>=c[i])
            {
                temp=c[i]-temp+1;
                v[++cnt]=temp;
                w[cnt]=b[i]*temp;
                bef[cnt]=i;
                temp<<=1;
            }
        }
        for(int i=1;i<=cnt;i++)
            for(int j=m;j>=w[i];j--)
            {
                if(f[j]>f[j-w[i]]+v[i])
                {
                    f[j]=f[j-w[i]]+v[i];
                    cun[i][j]=true;
                }
            }
        printf("%d
    ",f[m]);
        temp=m,ji=cnt;
        while(temp)
        {
            while(!cun[ji][temp]&&ji) --ji;
            temp-=w[ji];hole[bef[ji]]+=v[ji];
            --ji;
        }
        for(int i=1;i<=n;i++)
            printf("%d ",hole[i]); 
        return 0;
    }
  • 相关阅读:
    安装oh-my-zsh
    Ubuntu下安装2017版QQ
    Ubuntu安装Git
    链接libtorrent库时出现的问题
    ubuntu 下重装mysql若干问题
    最简单的epoll的使用范例 : 监听 标准输入 ,并将数据回显到终端
    [转]Linux下CodeBlocks的交叉编译
    各种免费素材下载站点
    Qt5:图片彩色键控,设置图片中指定颜色的像素为透明
    C++:预处理指令
  • 原文地址:https://www.cnblogs.com/cold-cold/p/10015491.html
Copyright © 2011-2022 走看看