東東與 ATM(多組揹包)

問題描述

一家銀行計劃安裝一臺用於提取現金的機器。
機器能夠按要求的現金量發送適當的賬單。
機器使用正好N種不同的面額鈔票,例如Dkk=1,2,,ND_k,k = 1,2,…,N,並且對於每種面額DkD_k,機器都有nkn_k張鈔票。
例如,
N=3N = 3,
n1=10D1=100n_1 = 10,D_1 = 100,
n2=4D2=50n_2 = 4,D_2 = 50,
n3=5D3=10n_3 = 5,D_3 = 10
表示機器有10張面額爲100的鈔票、4張面額爲50的鈔票、5張面額爲10的鈔票。
東東在寫一個 ATM 的程序,可根據具體金額請求機器交付現金。
注意,這個程序計算程序得出的最大現金少於或等於可以根據設備的可用票據供應有效交付的現金。

Input

程序輸入來自標準輸入。 輸入中的每個數據集代表特定交易,其格式爲:CashNn1D1n2D2...nNDNCash \quad N\quad n_1\quad D_1\quad n_2\quad D_2\quad ...\quad n_N\quad D_N其中0 <= Cash <= 100000是所請求的現金量,0 <= N <= 10是 紙幣面額的數量,0 <= nkn_k <= 1000是Dk面額的可用紙幣的數量,1 <= DkD_k <= 1000,k = 1,N。 輸入中的數字之間可以自由出現空格。 輸入數據正確。

Output

對於每組數據,程序將在下一行中將結果打印到單獨一行上的標準輸出中。

Sample input

735 3  4 125  6 5  3 350
633 4  500 30  6 100  1 5  0 1
735 0
0 3  10 100  10 50  10 10

Sample output

735
630
0
0

Hint

第一個數據集指定一筆交易,其中請求的現金金額爲 735。 機器包含3種面額的紙幣:4張鈔票 125、6張鈔票 5和3張鈔票 350。 機器可以交付所需現金的確切金額。
在第二種情況下,機器的票據供應不能滿足所要求的確切現金數量。 可以交付的最大現金爲 630。 請注意,在機器中組合鈔票以匹配交付的現金有多種可能性。

在第三種情況下,機器是空的,沒有現金交付。 在第四種情況下,請求的現金金額爲 0,因此機器不交付現金。

解題思路

這是一個裸的多重揹包題,Cash是揹包容量,nkn_k是物品數量,DkD_k是物品價值,也是物品重量。這裏用到了多重揹包的二進制拆分優化,當nkn_k的數量極大時,能做到較高的優化。

優化方法是利用每個正整數都可以由20,21,22,23,2^0, 2^1, 2^2, 2^3,\cdots組成,我們可以將一個數拆分。比如7=(111)27=(111)_2,那麼我們就可以將7拆分成100, 010, 001,也就是4,2,1。這三個數可以組成1-7之間的任意數。所以7個物品就拆分成了三個物品,第一個物品時4倍單價,第二個是2倍單價,第三個是單價。這樣這三種物品是否選擇就可以構成1-7個物品是否選擇。

那麼當我們要拆分的數是13的話怎麼處理?由於13=(1101)213=(1101)_2,我們不可能將其拆分成1000,0100,0001。因爲這樣有部分數據無法表示,比如2就沒有辦法表示。我們對這個的處理方法是將13化爲7+6。其中7用上面的拆分方法可以表示1-7之間的任意數。第四個物品就是6倍的單價。如果第四個物品不選,前三個就可以構成選擇1-7件物品的情況,如果第6種物品選擇,就是1+67+61+6\sim 7+6,代表了選擇7-13個的情況。這樣13就拆成了四個物品。

nkn_k極大的時候,我們的效率能得到極大的提升,例如nk=255n_k=255時,如果不使用二進制拆分,那麼這種物品就要枚舉1-255種情況分別表示第1個是否選擇,第2個是否選擇… … 。但是如果我們使用了二進制拆分,那麼就可以拆分成8個物品,第ii個物品的價格是2i12^{i-1}倍的單價。

完整代碼

//#pragma GCC optimize(2)
//#pragma G++ optimize(2)
//#include <bits/stdc++.h>
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <climits>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;

const int maxn=10000+10;
const int maxm=100000+10;
int cash,N,n[11],d[11],dd[maxn],dp[maxm];
int getint(){
    int x=0,s=1; char ch=' ';
    while(ch<'0' || ch>'9'){ ch=getchar(); if(ch=='-') s=-1;}
    while(ch>='0' && ch<='9'){ x=x*10+ch-'0'; ch=getchar();}
    return x*s;
}
int main(){
    //ios::sync_with_stdio(false);
    //cin.tie(0);
    while(cin>>cash>>N){
        for (int i=1; i<=N; i++)
            cin>>n[i]>>d[i];
        int cnt=0;
        for (int i=1; i<=N; i++){//二進制拆分
            int t=n[i];
            for (int k=1; k<=t; k<<=1){
                cnt++;
                dd[cnt]=k*d[i];
                t-=k;
            }
            if(t>0){
                cnt++;
                dd[cnt]=t*d[i];
            }
        }
        for (int i=1; i<=cnt; i++)//普通01
            for (int j=cash; j>=dd[i]; j--)
                dp[j]=max(dp[j],dp[j-dd[i]]+dd[i]);
        printf("%d\n",dp[cash]);
        memset(dp,0,sizeof(dp));
    }

    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章