1. 程式人生 > >[dp]多重部分和問題

[dp]多重部分和問題

多重部分和問題:

有n種不同大小的數字ai,每種各有mi個,判斷是否可以從這些數字中選出若干使它們的和恰好為k。

限制條件:

1 <= n <= 100

1 <= ai,mi <= 100000

a <= K <= 100000

樣例輸入:

n = 3

a = {3, 5, 8}

m= {3, 2, 2}

K=17

輸出:

Yes  (3*3+8=17)

這個問題可以用dp求解,不過如何定義遞推關係會影響到最終的複雜度。首先我們看一下如下的定義:

dp[i+1][j] = 用前i種數字能否加和成j

為了用前i種數字加和成j,也就需要能用前i-1種數字加和成 j, j-ai, ……,j-mi*ai 中的某一種。由此我們可以定義如下遞推關係:

dp[i+1][j] = 0 (k<=k<=mi 且 k*ai <= j 時存在使dp[i][j-k*ai]為真的k)

int n; //數列的長度
int K; //目標的和數
int a[MAXN]; //數字值
int m[MAXN]; //每種數字的個數
int dp[MAXN][MAXN];//dp陣列

void solve()
{
    dp[0][0] = true;
    for (int i = 0; i < n; i++)
        for(int j = 0; j <= K; j++)
            for(int k = 0; k<=m[i] && k*a[i]<=j;i++)
                dp[i+1][j] = dp[i][j-k*a[i]] | dp[i+1][j];
    if (dp[n][K])printf("yes\n");
}

從複雜度上來說,這個演算法的複雜度是(KΣimi),並不夠好。一般用dp求bool結果的話會有不少浪費,同樣的複雜度通常能獲得更多資訊。在這個問題中,我們不光求出能否得到目標的和數,同時把得到ai時這個數還剩下多少個可以使用給計算出來,這樣就可以減少複雜度。