POJ 1276 Cash Machine


Cash Machine

  A Bank plans to install a machine for cash withdrawal. The machine is able to deliver appropriate @ bills for a requested cash amount. The machine uses exactly N distinct bill denominations, say Dk, k=1,N, and for each denomination Dk the machine has a supply of nk bills. For example, 

  N=3, n1=10, D1=100, n2=4, D2=50, n3=5, D3=10 

  means the machine has a supply of 10 bills of @100 each, 4 bills of @50 each, and 5 bills of @10 each. 

  Call cash the requested amount of cash the machine should deliver and write a program that computes the maximum amount of cash less than or equal to cash that can be effectively delivered according to the available bill supply of the machine. 

Notes: 
  @ is the symbol of the currency delivered by the machine. For instance, @ may stand for dollar, euro, pound etc. 
Input
  The program input is from standard input. Each data set in the input stands for a particular transaction and has the format: 

  cash N n1 D1 n2 D2 ... nN DN 

  where 0 <= cash <= 100000 is the amount of cash requested, 0 <=N <= 10 is the number of bill denominations and 0 <= nk <= 1000 is the number of available bills for the Dk denomination, 1 <= Dk <= 1000, k=1,N. White spaces can occur freely between the numbers in the input. The input data are correct. 
Output
  For each set of data the program prints the result to the standard output on a separate line as shown in the examples below. 
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
  The first data set designates a transaction where the amount of cash requested is @735. The machine contains 3 bill denominations: 4 bills of @125, 6 bills of @5, and 3 bills of @350. The machine can deliver the exact amount of requested cash. 

  In the second case the bill supply of the machine does not fit the exact amount of cash requested. The maximum cash that can be delivered is @630. Notice that there can be several possibilities to combine the bills in the machine for matching the delivered cash. 

  In the third case the machine is empty and no cash is delivered. In the fourth case the amount of cash requested is @0 and, therefore, the machine delivers no cash.
解題思路:
  本題有多組測試數據,每組數據占一行,包括現金cash, 票據種類n,之后跟隨n組數據每組包括票據數量,票據金額。要求輸出小於等於現金的票據最大金額。
  可以將本題轉化為0 1背包問題,背包容量為現金數cash,背包內容物價值與占空間數都為票據面值。
  基本思路是將每個面值的票據拆分,由於票據數量較大,直接拆分運算時會超時,所以我們對拆分進行優化。
 
二進制優化:
  假設某一面值的票據有100張,我們並不需要將100張票據全部加入運算數組,我們將100拆分為數個小於100的數字,使這些數字可以構成1 ~ 100中任意一個數字。這里用到了一個數論的小知識:1,2,4 ~ 2^n 可以組成1 ~ 2^(n + 1) - 1之間的任意數,100便可分解為1,2,4,8,16,32,(取2的n次冪(二進制數),不能取到64,因為拆分100的話所有分解的數加起來不能超過100) 37(前面取到的1 ~ 32已經可以表示63以內所有的數了,那么再補上100 - 63 = 37這個數后就可以取到所有1 ~ 100的數了)。繼續分析,假設這100張票據面值都為2,那我們就可用到我們分解出來的數字,根據這些數字我們把100張票分解1張,2張,4張,8張,16張,32張,37張這7組,這樣我們便可以把100張面值為2的票據看成面值為2,4,8,16,32,64,74的7張票據。將這7張票據加入運算數組在運算時比起100張票據就要節約很多時間。
  至於為什么1,2,4,8,16,32,37可以代替100,這很簡單,之前已經寫過了新的數組可以表示1 ~ 100所有數組,那么在運算中,我們如果需要拿5張面值1,在這里就和拿一張面值1一張面值4有同樣效果拿其他所有1 ~ 100的數皆是這個道理。
 
背包思路:
  動態規划,令dp[ j ]表示chsh為 j 時能得到的票據的最大金額。

  對於第 i 張票據,有用或不用兩種方案。

  1、用第 i 塊票據,問題轉化為計算背包容量為 j - value[ i ] 在前i - 1張票據中取得最大金額問題dp[ j ]的值為前i - 1張票據中取得的最大金額 + 第 i 張票據的金額。

  2、不用第 i 張票據,問題轉化為背包容量為 j 時在前i - 1張票據中取得最大金額問題,dp[ j ]的值為前i - 1張票據中取得的最大金額。

  動態轉移方程:dp[ j ] = max(dp[ j ], dp[ j - value[ i ] ] + value[ i ])

  邊界為用前0張票據,最大金額為0,枚舉所有票據,每次從最大背包容量開始逆序枚舉便可獲得答案。

 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 const int maxn = 11 * 1000;
 4 int dp[maxn];
 5 int value[maxn];
 6 //value記錄票據面值
 7 int main()
 8 {
 9     int cash;   //cash為現金金額(即背包容量)
10     while(scanf("%d", &cash) != EOF){
11         memset(value, 0, sizeof(value));
12         //初始化票據面值數組都為0
13         int n, cnt = 1;
14         scanf("%d", &n);    //輸入票據種類數
15         for(int i = 0 ; i < n; i++){
16             int num, denomination;
17             scanf("%d%d", &num, &denomination);
18             //輸入每種票據的數量與面值
19             //二進制拆分num
20             int j = 1;  //拆分的第一個數字為1
21             while(num >= j){    
22             //所有拆分后數字的和不超過num
23             //我們可以每拆分一個數就用num減去它直到num小於想要拆分的下一個數
24                 value[cnt++] = j * denomination;    //將拆分后的面值計入value
25                 num -= j;   //num減去當前拆分的數字
26                 j *= 2; //下一個要拆分的數字為當前數字的兩倍
27             }
28             value[cnt++] = num * denomination;  
29             //最后補上差的數字,並記錄其組成的面值
30         }
31         cnt--;
32         memset(dp, 0, sizeof(dp));
33         //初始化邊界
34         for(int i = 1; i <= cnt; i++){  //枚舉票據
35             for(int j = cash; j >= value[i]; j--){  //逆序枚舉背包容量
36                 dp[j] = max(dp[j], dp[j - value[i]] + value[i]);
37             }
38         }
39         int ans = 0;
40         for(int i = 0; i <= cash; i++){ //找到最大值
41             ans = max(ans, dp[i]);
42         }
43         printf("%d\n", ans);
44     }
45     return 0;
46 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM