1. 程式人生 > >poj 1742 Coins 【多重揹包+二進位制拆分優化】

poj 1742 Coins 【多重揹包+二進位制拆分優化】

Coins

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 3969    Accepted Submission(s): 1578


 

Problem Description

Whuacmers use coins.They have coins of value A1,A2,A3...An Silverland dollar. One day Hibix opened purse and found there were some coins. He decided to buy a very nice watch in a nearby shop. He wanted to pay the exact price(without change) and he known the price would not more than m.But he didn't know the exact price of the watch.

You are to write a program which reads n,m,A1,A2,A3...An and C1,C2,C3...Cn corresponding to the number of Tony's coins of value A1,A2,A3...An then calculate how many prices(form 1 to m) Tony can pay use these coins.

Input

The input contains several test cases. The first line of each test case contains two integers n(1 ≤ n ≤ 100),m(m ≤ 100000).The second line contains 2n integers, denoting A1,A2,A3...An,C1,C2,C3...Cn (1 ≤ Ai ≤ 100000,1 ≤ Ci ≤ 1000). The last test case is followed by two zeros.

Output

For each test case output the answer on a single line.

Sample Input

3 10 1 2 4 2 1 1 2 5 1 4 2 1 0 0

Sample Output

8 4

題意:給面值不同,有固定個數的硬幣,能有多少種不同的不同總面值的組合方式;

思路:把多重揹包問題用二進位制拆分成 01 揹包求解,再加上一點剪枝;

先解釋一下多重揹包如何優化,這裡有一個數學的結論: 1到n以內的數字,能夠通過 n 內的進位制數組合得到,比如 9以內的二進位制數有 1 2 4 8,可以自己在草稿紙上試一下, 3能通過 1 + 2 得到,5能通過1 + 4 得到,6能通過 2 + 4得到,所以,我們可以利用二進位制數的拆分求出所有 n 以內的數;下面是拆分的過程:

9 - 1 = 8;  8 - 2 = 6; 6 - 4 = 2 ; 2 - 8  < 0; 那麼要求的 9 以內的二進位制數就是  1,2, 4,2,這幾個二進位制是能夠組成 9以內包括 9 的所有整數,然後通過 w[ i ] 乘以這些二進位制數,把他們當作一件物品來做01揹包的處理,當然,如果沒有用二進位制拆分的話,可以直接把9拆分成  1,2,3,4,5,6,7,8,9 然後乘以w[ i ] 當作一件物品來做01揹包處理,這種依次拆分的時間複雜度和不拆直接用多重揹包處理是一樣的,但是,為什麼9個數字,只需要1,2,4,2這四個二進位制數就夠了呢?(這個問題暫時還沒辦法解決,能力有限,目前只要知道,只要拆分的二進位制數字能夠組成 n 以內的所有數的情況下,轉化為單個物品做01揹包的處理就行了),下面是程式碼:

#include<iostream>
#include<cstdio>
#include<cstring>

using namespace std;

#define Maxn 100010
#define INF 0x3f3f3f3f

int dp[Maxn],v[105],num[105],tmp_v[Maxn],book[Maxn];

int main(void)
{
   // freopen("in.txt","r",stdin);
   // freopen("out.txt","w",stdout);
    int kind,max_p;
    while (scanf("%d%d",&kind,&max_p) != EOF) {
        if(!kind && !max_p) break;
        for (int i = 1; i <= kind; ++i) scanf("%d",&v[i]);
        for (int i = 1; i <= kind; ++i) scanf("%d",&num[i]);

        memset(dp,0,sizeof(dp));
        
        int cnt = 1;  // 用單獨的陣列存取拆分後的所有面值
        for (int i = 1; i <= kind; ++i) {
            for (int j = 1; j <= num[i]; j <<= 1) {
                tmp_v[cnt] = j*v[i];
                num[i]-=j;
                cnt++;
            }
            if(num[i] > 0) tmp_v[cnt++] = num[i]*v[i];
        }
        
        //用上面拆分後的陣列做01揹包的處理
        // 這裡並沒有用01揹包去求最大值,而是把能夠滿足條件的狀態dp 賦值為1,下標就是能夠組合得到的總面值,
        //否則為 0 ,不存在這樣的面值
        memset(book,0,sizeof(book));
        int max_ = 1;  
        int ans = 0;
        for (int i = 1; i < cnt; ++i) {
                max_+=tmp_v[i];
                max_ = min(max_p,max_);
            for (int j = max_; j >= tmp_v[i]; --j) {   //j的迴圈做了一些剪枝,因為遍歷是從右往左的,在紙上模擬一下
                if(dp[j]) continue;                     //就會明白,當前j遍歷的最大值就是把以前用過的面值的累加,最小值就是
                if(j == tmp_v[i]) dp[j] = 1;            //當前物體的面值;
                else if(j-tmp_v[i] > 0 && dp[j-tmp_v[i]]) dp[j] = 1;
                if(!book[j] && dp[j]) { ans++; book[j] = 1; }
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

N天后,對上面的程式碼進行改進,同樣的一道題,要不是遇到poj,我可能以為上面的程式碼就很完美了,事實上,上面的程式碼如果交的poj的coins的話,會時間超限,而上面的程式碼只能在HDU的題庫上AC,不得不說,這題的資料測試上,poj要強HDU很多,下面看看兩個能在poj  AC的程式碼,第一種的用時是 2800ms左右,這裡的方法其實和上面的是一樣的,只是在一些個別資料上面加上了完全揹包來優化了時間;方法:針對一些硬幣的數量,我們把他們統一做二進位制拆分然後做01揹包處理,假設一種硬幣的數量為 10^5,那麼二進位制拆分得到的單個物品的數量就是  log 2(10^5),不做常數優化的01揹包的情況下,一個數量為10^5的硬幣時間複雜度大概就是  15*V  (V是硬幣組合的最大值),假設V是題目的上限 10^5,那麼單個硬幣的時間複雜度就是1.5*10^6,算是比較大的了,一般時間複雜度為 10^8 為比較適合的;對於這類數量巨大的資料我們如何處理? 其實仔細想想,但凡硬幣的數量*硬幣的價值的總和大於規定的 V 的時候,我們是不是可以直接把他當做完全揹包處理,如果用完全揹包的話,一個物品不用拆分就能完成這個硬幣的dp操作,時間複雜度為一個V,單個數據就能快上15倍這個樣子,完全揹包的特點就是物品的數量可以無限取,但事實上,物品的數量*物品的價值大於規定的V的值得時候,我們是直接不考慮的;所以和上面的程式碼不同的就是,把硬幣的數量分成兩類,一類就是數量*面值大於 V的,把他當做完全揹包處理,小於或者等於的話,就繼續做上面程式碼的二進位制拆分的01揹包處理;下面給出程式碼:

#include<cstdio>

using namespace std;

#define Maxn 100010

int dp[Maxn],N,V,price[105],num[105];

void com_beg(int x) {   // 完全揹包
    for (int j = price[x]; j <= V; ++j) {  // 這裡 j=price[x] ,是一點剪枝,建議自己獨立思考一下,
                                            // 為什麼這樣剪枝
        if(!dp[j] && j >= price[x] && dp[j-price[x]]) dp[j] = 1;
    }
}

void zero_one (int x) {  // 01揹包
    for (int j = V; j > 0; --j) {  
        if(j < x) break;
        if(!dp[j] && dp[j-x]) dp[j] = 1;
    }
}

int main(void)
{
    while (scanf("%d%d",&N,&V) != EOF) {
        if(!N && !V) break;
        for (int i = 0; i < N; ++i) scanf("%d",&price[i]);
        for (int i = 0; i < N; ++i) scanf("%d",&num[i]);
        for (int i = 1; i <= V; ++i) dp[i] = 0; // 這裡初始化是可以用memeset的,但陣列開的大
        dp[0] = 1;                              // 這樣也許會快一丟丟把。。
        // 這裡dp[0] 其實是一個確切的值,想想,揹包數量為0的時候,不放不就滿足條件了嗎
        for (int i = 0; i < N; ++i) {
            if(num[i]*price[i] >= V) {
                com_beg(i);
            }
            else {
                for (int j = 1; j <= num[i]; j <<= 1) {
                    if(j*price[i] <= V) zero_one (j*price[i]);
                    num[i]-=j;
                }
                if(num[i] > 0 && num[i]*price[i] <= V) zero_one(num[i]*price[i]);
            }
        }
        int ans = 0;
        for (int i = 1; i <= V; ++i) if(dp[i]) ans++;
       printf("%d\n",ans);
    }
    return 0;
}

上面就是其中的一種寫法,但時間還是花費有點多,第二種方法有一個(好像很牛B的名字)多重揹包可行性的名字;下面我解釋一下這種方法是怎樣的一個原理(是我自己理解的,也許不對,可以看看參考一下);

多重揹包與完全揹包和01揹包最大的不同就是有限定的數量,也就是說,每一種物品限定的數量是不定的,我們回想一下最基本的多重揹包是如何寫的: 在01揹包的基礎上,多加一個迴圈k,k的作用就是一個限制的作用,當k 大於當前物品的數量的時候就退出,然後下一個又繼續;如果我們能用別的方法代替這個k去限定物品的數量,也許就可以把這個k的迴圈去掉了;多重揹包可行性的問題其實是一個系列的問題,都是問你一個物品能有多少種組合,而不是問你最大重量或者最小重量是多少,對於01揹包和完全揹包的問題上,我們用dp來表示當前的狀態,並且把當前的子問題的答案儲存到dp數組裡頭,比如,我們要求最大值,那麼就把當前狀態的子問題的最大值放進dp裡面,那這道coins的子問題只是問我們能否組合得到,所以,可以用0和1來表示是或者否,而多重揹包可行性的問題就是利用了這一點,把硬幣的數量值放在dp裡頭,把dp的資料用一個特別的值隔開,一邊表示能組合一邊表示不能,而且這個資料同時還能夠儲存硬幣的數量,來限定這個硬幣的使用;下面有幾張圖:

假設硬幣有 兩種 kind 1 和 kind 2;kind 1 的價值為1,另一個為2,數量分別為 2 和 3,V的值是6,dp【j】的j值表示的意思和上面的一樣,就是罐子裡面硬幣的總值;

kind 1 的情況,kind 1 是第一個放入的,所以毫無疑問,能組合的總值就是kind 1 的倍數,我們一維 dp 初始化為 0 ,表示的是硬幣還一次沒用,當 j的 1的時候,罐子內是可以放一枚 kind 1 硬幣的,然後在dp + 1,表示用了一枚了,並且用另外一個數值來存能夠滿足條件的組合,就是book[ 1 ] = 1; 然後 j = 2 的時候,dp【2】 == 0 && book【2】 == 0  ,說明硬幣總值為 2 的罐子還沒出現過(這裡要注意,我們每次一個硬幣只放一個),然後我們找 dp【j - price[ 1 ]】也就是 dp [ 2 - 1 ] ,發現是存在這種總值的罐子,那麼我們直接在這個罐子裡面投一枚  kind 1 ,就能得到 j = 2,硬幣總值為 2  的罐子了,然後dp 【2】 = dp【1】+ 1;為什麼 + 1?這裡 + 1的意思是完成 j = 2 的時候用了已經有一枚kind 1 的總值為 1 的罐子:

既然dp【2】 已經到kind 1 數量的上限了,是不是就意味著j = 2 後面就不用遍歷了呢?並不是,下面看看 kind 2 的情況就明白了:

我們先看 j = 2 的時候,j = 2的時候,顯然上一次就已經用 book 記錄了 book【2】 == 1;也就是說,即使不加kind 2的硬幣,也能組合得到 j == 2 的罐子,所以不用放一枚kind 2 硬幣,然後我們在看 j = 3 的時候,顯然 book【3】 == 0,就去看dp【j - price【2】】的值,也就是 dp【3 - 2】 = dp 【1】,顯然 book【1】 == 1 && dp【1】 == 0,如果當前dp【1】 == 0,說明在不用kind 2硬幣的情況下存在 j == 1的罐子的組合,那麼我們在 j = 1 這個罐子裡頭放一個  kind 2 的硬幣,那就能夠有j = 3 的罐子了,這裡我們只用了一枚 kind 2的硬幣,因為組合得到的 j = 1的罐子沒用kind 2的硬幣(判斷dp【1】就能知道),j = 4也是一樣的道理,j = 5的時候,發現book【5】== 0;那麼我們就找 book【3】,book【3】= 1,注意,上圖中第一行雖然dp【3】 == 0,但是在第二行的dp【3】是不為0的,說明存在j = 3的罐子,這時候發現dp【3】 = 1,說明組合得到 j = 3的時候用了一枚,那麼組合得到dp【5】的時候就應該dp【5】 = dp【3】 + 1;這時候dp【5】 = 2;說明組合dp【5】用了2了兩枚kind 2的硬幣,以此類推,如果dp【j】大於當前硬幣的數量的時候,就能直接判斷不可行了;下面給出程式碼:

#include<cstdio>
#include<cstring>

using namespace std;

#define Maxn 100010

int dp[Maxn],N,V,price[105],num[105],book[Maxn];

int main(void)
{
    while (scanf("%d%d",&N,&V) != EOF) {
        if(!N && !V) break;
        for (int i = 1; i <= N; ++i) scanf("%d",&price[i]);
        for (int i = 1; i <= N; ++i) scanf("%d",&num[i]);
        for (int i = 0; i <= V; ++i) dp[i] = 0;
        memset(book,0,sizeof(book));
        book[0] = 1; //  0這種組合是無論如何都存在的

        for (int i = 1; i <= N; ++i) {
            for (int k = 1; k <= V; ++k) dp[k] = 0;
            for (int j = price[i]; j <= V; ++j) {
                if(!book[j] && book[j - price[i]] && dp[j-price[i]] < num[i]) {
                    dp[j] = dp[j-price[i]] + 1;
                    book[j] = 1;
                }
            }
        }
        int ans = 0;
        for (int i = 1; i <= V; ++i) if(book[i]) ans++;
       printf("%d\n",ans);
    }
    return 0;
}

相關推薦

poj 1742 Coins 多重揹包+二進位制拆分優化

Coins Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 3969    Accepted Submiss

poj 1742 Coins 多重揹包可重性

題意:有n種不同大小的數字ai,每種各mi個,判斷是否可以從這些數字之中選出若干使他們的和恰好為k。 輸入: n=3 a={3,5,8} m={3,2,2} k=17 輸出: YES(3*3

POJ 1742 Coins 多重背包DP

數量 printf can 硬幣 ring editable urn content std 題意:有n種面額的硬幣。面額、個數分別為A_i、C_i,求最多能搭配出幾種不超過m的金額? 思路:dp[j]就是總數為j的價值是否已經有了這種方法,如果現在沒有,那麽我們就一個個硬

jzoj100044-abcd多重揹包,二進位制壓縮,dp

正題 題目大意 給出長度為 n n n的序列

POJ 1742 Coins ( 經典多重部分和問題 && DP || 多重背包 )

count ... mes opened view display 是什麽 [] sizeof 題意 : 有 n 種面額的硬幣,給出各種面額硬幣的數量和和面額數,求最多能搭配出幾種不超過 m 的金額? 分析 : 這題可用多重背包來解,但這裏不討論這種做法。 如果之前有

HDU2844 Coins多重揹包

Coins Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 20670  

多重揹包+二進位制優化ACM-ICPC 2018 焦作賽區網路預賽 - K. Transport Ship

題目連結<https://nanti.jisuanke.com/t/31720> 題意: 給出若干個船,每個船都有的容量,和的數量。有Q次詢問,給出一定體積的貨物,問你恰好裝滿的方案數量。 題解: 很明顯的多重揹包,但是要加上二進位制優化。 所謂二進位制優

Cash Machine (POJ 1276)(多重揹包——二進位制優化

題意:給你一個最大金額m,現在有n種類型的紙票,這些紙票的個數各不相同,問能夠用這些紙票再不超過m的前提下湊成最大的金額是多少? 題解:寫了01揹包直接暴力,結果T了,時間複雜度太高了,要跑外迴圈

POJ-1742-Coins-優化多重揹包

題目傳送門 題意: 有n種硬幣,這n種硬幣的價值為coin[i].val,第i種硬幣的個數為coin[i].num個,問能用這些硬幣支付多少不超過m的價格? 思路: 多重揹包裸題,但是要進行空間和時間上的優化,否則會MLE和TLE。 空間優化:通過特定的順序滾動陣列,來降維。具體可以參考

POJ 1014 Dividing (多重揹包問題+遞迴)模板

Marsha and Bill own a collection of marbles. They want to split the collection among themselves so that both receive an equal share of

Cash Machine POJ - 1276 多重揹包二進位制優化

題意:多重揹包模型  n種物品 每個m個  問揹包容量下最多拿多少 這裡要用二進位制優化不然會超時   1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4

Dividing POJ - 1014 多重揹包二進位制優化

多重揹包模型  寫的時候漏了一個等號找了半天 i<<=1 !!!!!!     1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 usin

POJ 1742 Coins 混合三種揹包問題

                    好吧,這種關於錢幣組合型別的題目原來是可以看成揹包來做的。。。這一道題目就是可以看成一個混合揹包來做。。假若數量乘以價值比m要大的話,那就可以看成是一個完全揹包來做。否則的話就是一個多重揹包嘍。思想倒是很好理解。。而且這裡只需要我們判

甜點(多重揹包+二進位制優化)

描述 小z準備舉辦一個比賽。他需要提供一些甜點給參賽者來補充能量。每種甜品有一定的能量ti和大小ui,且每種甜點最多有vi個。 小z準備用箱子來包裝甜點。箱子可以容納一定體積的甜點且需要一定的費用。小z有一種魔法,可以將一個甜點分成多份裝在箱子裡,最後再合在一起(但合成之後必須是完整的一個)

多重揹包二進位制優化(wzk吃小雞腿)

問題 B: WZK吃小雞腿(chicken) 時間限制: 1 Sec   記憶體限制: 128 MB 提交: 53   解決: 23 [ 提交][ 狀態][ 討論版

多重揹包二進位制優化分析

列出一個模板供參考: #include<bits/stdc++.h> #include<time.h> #define INF 0x3f3f3f3f using namespace std; #define N 1000 #define M 100000000 int

HDU-1059-Dividing(多重揹包+二進位制優化

Problem DescriptionMarsha and Bill own a collection of marbles. They want to split the collection among themselves so that both receive an equal share of t

2844 Coins多重揹包

題目連結 題意:     有n種硬幣,給出每種硬幣的幣值A[i]和數量C[i],求1到m中能組成幾個值 思路:     多重揹包問題,就是完全揹包但是限制了每種的數量; #include <iostream> #include <cstrin

HDU 1059.Dividing多重揹包+篩選(DP)3月17

Dividing Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 22051    Accepted Su

焦作網路賽 Transport Ship(多重揹包二進位制劃分+DP)

There are NN different kinds of transport ships on the port. The i^{th}ith kind of ship can carry the weight of V[i]V[i]