HDU 1074 要求安排作業順序使扣分最少。每項作業有截止日 D 與所需天數 C,遲交一天扣 1 分。N ≤ 15,適合狀態壓縮 DP (Bitmask DP)。本文詳細推導狀態定義、轉移方程、前驅記錄、路徑回溯,並提供完整 C++ 代碼。
mask 表示已完成作業集合,第 i 位為 1 表示作業 i 已做2^N (N≤15 → 32768,極小)dp[mask]:完成 mask 所含作業的最小扣分time[mask]:完成 mask 所需總天數pre[mask]:達成 mask 最後做的作業索引對於狀態 mask,枚舉其中每個作業 i 作為「最後完成」的作業:
prev = mask ^ (1 << i) // 去掉作業 i 的前驅狀態
cur_time = time[prev] + C[i] // 完成 i 後的總天數
penalty = max(0, cur_time - D[i]) // 作業 i 的扣分
total = dp[prev] + penalty
若 total < dp[mask]:
dp[mask] = total
time[mask] = cur_time
pre[mask] = i
初始:dp[0] = 0, time[0] = 0。按 mask 從 1 遞增至 (1<
(mask >> k) & 1 或 mask & (1<mask | (1<mask & ~(1<mask ^ (1<for (int sub = mask; sub; sub = (sub-1) & mask)#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAXN = 15;
const int MAXS = 1 << MAXN;
int dp[MAXS], tm[MAXS], pre[MAXS];
int D[MAXN], C[MAXN];
char name[MAXN][110];
void print_path(int mask) {
if (!mask) return;
print_path(mask ^ (1 << pre[mask]));
puts(name[pre[mask]]);
}
int main() {
int T; scanf("%d", &T);
while (T--) {
int N; scanf("%d", &N);
for (int i = 0; i < N; i++)
scanf("%s %d %d", name[i], &D[i], &C[i]);
int ALL = 1 << N;
fill(dp, dp + ALL, INF);
fill(tm, tm + ALL, 0);
dp[0] = 0;
for (int mask = 1; mask < ALL; mask++) {
for (int i = 0; i < N; i++) if (mask & (1 << i)) {
int prev = mask ^ (1 << i);
int cur_t = tm[prev] + C[i];
int pen = max(0, cur_t - D[i]);
if (dp[prev] + pen < dp[mask]) {
dp[mask] = dp[prev] + pen;
tm[mask] = cur_t;
pre[mask] = i;
}
}
}
printf("%d\n", dp[ALL - 1]);
print_path(ALL - 1);
}
return 0;
}
i 從 0 到 N-1,pre 記錄最後做的作業,回溯時遞迴先印前驅再印當前,自然產生字典序最小解for mask in 0 .. (1<HDU 1074 是狀態壓縮 DP 入門最佳範例,掌握其「集合狀態、枚舉最後一步、前驅回溯」三步法,即可解決大多數 N≤20 的排列/子集優化問題。