給定 n 種面值的硬幣,第 i 種面值為 A[i],數量為 C[i]。求能組合出的不同金額總數(金額 ≤ m)。
資料範圍:n ≤ 100, m ≤ 100,000, A[i]·C[i] ≤ m。
傳統多重背包二進制拆分或單調隊列優化在 POJ 上會 TLE。需採用 O(N·M) 狀態壓縮 DP,利用 used[j] 記錄「湊出金額 j 時,當前硬幣 i 已使用次數」。
f[j] = 1 表示金額 j 可湊出。i,初始化 used[0..m] = 0。j = A[i] .. m 遞增掃描:f[j] == 0 且 f[j-A[i]] == 1 且 used[j-A[i]] < C[i],f[j] = 1, used[j] = used[j-A[i]] + 1。used 限制次數)#include <stdio.h>
#include <string.h>
int main() {
int n, m;
while (scanf("%d %d", &n, &m) == 2 && (n || m)) {
int A[102], C[102];
for (int i = 0; i < n; i++) scanf("%d", &A[i]);
for (int i = 0; i < n; i++) scanf("%d", &C[i]);
static int f[100002]; // 靜態陣列避免棧溢位
static int used[100002];
memset(f, 0, sizeof(f));
f[0] = 1;
for (int i = 0; i < n; i++) {
memset(used, 0, sizeof(used)); // 每種硬幣重置 used
for (int j = A[i]; j <= m; j++) {
if (!f[j] && f[j - A[i]] && used[j - A[i]] < C[i]) {
f[j] = 1;
used[j] = used[j - A[i]] + 1;
}
}
}
int ans = 0;
for (int i = 1; i <= m; i++) if (f[i]) ans++;
printf("%d\n", ans);
}
return 0;
}
| 技巧 | 說明 |
|---|---|
遞增掃描 j | 允許同一硬幣在同一輪被多次使用(完全背包特性),配合 used 限制上限 |
used[j] 只記錄當前硬幣用量 | 不需二維陣列,空間 O(M) |
f[j]==0 判斷 | 避免重複更新,保證 O(N·M) 時間 |
| 靜態陣列 | 避免大陣列棧溢位,加快初始化 |
二進制拆分將物品數擴增為 O(log C[i]),再跑 0/1 背包(逆序掃描),實際操作數約 Σ log C[i] · m。當 C[i] 較大且 m=100,000 時,常數因子過大導致超時。本題專用的 used[] 方法實際操作數僅 n·m = 10^7 量級,極易通過。
O(n · m) ≈ 107 操作,極快。O(m) 兩個 int 陣列 ≈ 800 KB。此解法同樣適用於「求可組合金額種類數」的多重背包變體,是 OJ 多重背包經典模板之一。