1. 程式人生 > >POJ 1742 Coins ( 經典多重部分和問題 && DP || 多重背包 )

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

count ... mes opened view display 是什麽 [] sizeof

題意 : 有 n 種面額的硬幣,給出各種面額硬幣的數量和和面額數,求最多能搭配出幾種不超過 m 的金額?

分析 :

這題可用多重背包來解,但這裏不討論這種做法。

如果之前有接觸過背包DP的可以自然想到DP數組的定義 ==> dp[i][j] 表示使用前 i 種硬幣是否可以湊成面額 j 。

根據這樣的定義,則一開始初始化 dp[0][0] = true 最後統計 dp[n][1 ~ m] 為 true 的數量即為答案

狀態轉移方程為 dp[i][j] |= dp[i-1][ j - k*val[i] ] ( k 表示取 k 個第 i 種硬幣、val[i] 表示第 i 種硬幣的面額 )

轉移方程的意義不難理解,需要考慮當前的 dp[i][j] 可以從哪些狀態轉移而來,如下

使用第 i 種硬幣剛好湊成 j 的值應當為上個狀態( dp[i-1][] )合法的 j-val[i]、j-2*val[i]、j-3*val[i]....

故代碼應當為一個如下所示的三重循環,但是復雜度較高無法通過這題.....

技術分享圖片
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
const int maxn = 111;

bool dp[maxn][100005];
int num[maxn], val[maxn];

int main(void)
{
    
int N, C; while(~scanf("%d %d", &N, &C) && !(N==0 && C==0)){ for(int i=1; i<=N; i++) scanf("%d", &val[i]); for(int i=1; i<=N; i++) scanf("%d", &num[i]); memset(dp, false, sizeof(dp)); dp[0][0] = true; for(int i=1; i<=N; i++){
for(int j=0; j<=C; j++){ for(int k=0; k<=num[i] && k*val[i]<=j; k++){ dp[i][j] |= dp[i-1][j-k*val[i]]; } } } printf("%d\n", count(dp[N]+1, dp[N]+C+1, true)); } return 0; }
View Code

通常使用 dp 數組只記錄布爾值是種浪費的做法,一般就去考慮在保證正確性的情況下改變 dp 含義記錄更多信息去降低復雜度!

現將 dp 含義改變為 ==> dp[i][j] 表示用前 i 種硬幣湊成 j 時第 i 種硬幣最多還可以剩多少

挑戰書上是直接給出了定義,但是我更樂於探尋這玩意是什麽來的? 註:以下都是我自己的想法

想想上面的解法,好像會發現其實如果我當前考慮過 dp[i][j] = true 了,那麽 dp[i][j+val[i]]、dp[i][j+2*val[i]]、dp[i][j+3*val[i]]... 應該都為 true

而枚舉 j 的順序也恰好是從小到大,所以必定會枚舉到 dp[i][j+val[i]]、dp[i][j+2*val[i]]...,所以何不寫成如下這樣

for(int i=1; i<=N; i++){
    for(int j=0; j<=C; j++){
        dp[j] |= dp[j-val[i]];
    }
}

運行了樣例之後發現這樣的做法得出的答案比標準答案更大!為什麽?因為這樣的做法沒有考慮到數量,一種硬幣的數量是有限的

所以當 j+k*val[i] 的 k 超過了規定數量的時候就會發生錯誤,使得一些本該為 false 的 dp 數組值變成了 true,所以我們需要記錄數量!

技術分享圖片

復雜度為 O(n*m) 在 POJ 上跑了 2016MS

技術分享圖片
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
const int maxn = 111;

int dp[100005];
int num[maxn], val[maxn];

bool fun(int x)
{ if(x>=0) return true; return false; }

int main(void)
{
    int N, C;
    while(~scanf("%d %d", &N, &C) && !(N==0 && C==0)){

        for(int i=1; i<=N; i++) scanf("%d", &val[i]);
        for(int i=1; i<=N; i++) scanf("%d", &num[i]);

        memset(dp, -1, sizeof(dp));
        dp[0] = 0;

        for(int i=1; i<=N; i++){
            for(int j=0; j<=C; j++){
                if(dp[j] >= 0) dp[j] = num[i];
                else if(j < val[i] || dp[j-val[i]] <= 0) dp[j] = -1;
                else dp[j] = dp[j-val[i]] - 1;
            }
        }

        printf("%d\n", count_if(dp+1, dp+1+C, fun));
    }
    return 0;
}
View Code

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