HDU 3033 是分組背包的變體:k 個品牌(組),每組至少選一雙鞋,總預算 m,最大化收藏價值。若無法滿足至少選一約束,輸出 Impossible。
n 雙鞋,屬於 k 個品牌之一b、價格 cost、價值 valmdp[i][j]:考慮前 i 個品牌,花費 j 的最大價值(-1 表示不可達)
處理第 i 品牌的第 t 雙鞋(價格 c、價值 v):
dp[i][j] = max(dp[i][j], dp[i-1][j-c] + v)dp[i][j] = max(dp[i][j], dp[i][j-c] + v)這完美體現了「至少一雙」:第一雙必須從上一組來,後續可從本組疊加。
#include
#include
using namespace std;
#define MAX 11000
struct Shoe { int cost, val; };
Shoe shoes[11][MAX];
int dp[11][MAX], len[11];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, k;
while (cin >> n >> m >> k) {
memset(len, 0, sizeof(len));
memset(dp, -1, sizeof(dp));
for (int i = 1; i <= n; i++) {
int b, c, v;
cin >> b >> c >> v;
shoes[b][++len[b]] = {c, v};
}
dp[0][0] = 0;
int groups = 0;
for (int i = 1; i <= k; i++) {
if (len[i] == 0) break; // 某品牌無鞋,不可能
groups++;
for (int t = 1; t <= len[i]; t++) {
int c = shoes[i][t].cost;
int v = shoes[i][t].val;
for (int j = m; j >= c; j--) {
// 從上一組轉移(第一雙)
if (dp[groups-1][j-c] != -1)
dp[groups][j] = max(dp[groups][j], dp[groups-1][j-c] + v);
// 從本組轉移(第二雙+)
if (dp[groups][j-c] != -1)
dp[groups][j] = max(dp[groups][j], dp[groups][j-c] + v);
}
}
}
if (groups != k) { cout << "Impossible\n"; continue; }
int ans = -1;
for (int j = 0; j <= m; j++)
ans = max(ans, dp[groups][j]);
if (ans == -1) cout << "Impossible\n";
else cout << ans << "\n";
}
return 0;
}
| 環節 | 細節 |
|---|---|
| 品牌分組 | 用 len[b] 記錄每品牌鞋數,shoes[b][t] 存儲 |
| DP 初始化 | -1 表不可達,dp[0][0]=0 起始狀態 |
| 雙重轉移 | 同一雙鞋同時嘗試從 dp[i-1] 和 dp[i] 轉移 |
| 容量逆序 | j = m..c 保證 0-1 性質(同一雙鞋不重複用) |
| 品牌缺失檢查 | groups != k 直接 Impossible |
| 最終答案 | 遍歷 dp[groups][0..m] 取最大值 |
Input: 5 10000 3 1 4 6 2 5 7 3 4 99 1 55 77 2 44 66 3 10000 2 1 2 3 1 3 4 1 5 6 5 10000 3 1 1000 3 1 1000 4 2 100000 4 3 1000 3 3 1000 4 Output: 249 Impossible Impossible
| 變體 | 組內約束 | 轉移來源 |
|---|---|---|
| 標準分組背包 | 最多選一 | 僅 dp[i-1][*] |
| 本題(至少一) | 至少選一,可多選 | dp[i-1][*] (首項) + dp[i][*] (後續) |
| 完全背包分組 | 可多選,可不選 | dp[i-1][*] + dp[i][*] (全部) |
HDU 3033 通過「雙重轉移來源」優雅地解決了「至少選一」約束。這一技巧可推廣到:分組最少選 k 件、分組選擇數量範圍 [L, R] 等變體。核心思想:將約束分解為「第一件」與「後續件」的不同狀態來源。掌握此模式,分組背包變體不再懼怕。