1. 程式人生 > >P3423 [POI2005]BAN-Bank Notes

P3423 [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\n",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\n",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;
}