HDU 2546 是經典的 0-1 背包變體:食堂刷卡有「餘額 ≥ 5 即可任意消費」的特殊規則。求購買所有菜品後的最小餘額。
n 道菜,價格 price[i],每道買一次sumsum 開始,每次減去 price[i],中間狀態必 ≥ 5,終態可 < 5dp[j] 表示餘額 j 是否可達
// 物品價格按升序排序(關鍵!)
sort(price+1, price+n+1);
dp[sum] = 1; // 初始狀態可達
minRem = sum;
for i = 1..n:
for j = 5..sum: // 正序遍歷!保證用上一輪狀態
if dp[j]:
if j - price[i] >= 5:
dp[j - price[i]] = 1;
if j - price[i] < minRem:
minRem = j - price[i]; // 記錄最小可達餘額
#include
#include
#include
using namespace std;
#define MAX 100000
int n, price[MAX], sum, minRem, dp[MAX];
int main() {
while (scanf("%d", &n), n) {
for (int i = 1; i <= n; i++) scanf("%d", &price[i]);
scanf("%d", &sum);
sort(price + 1, price + n + 1);
memset(dp, 0, sizeof(dp));
dp[sum] = 1;
minRem = sum;
for (int i = 1; i <= n; i++)
for (int j = 5; j <= sum; j++)
if (dp[j]) {
if (j - price[i] >= 5)
dp[j - price[i]] = 1;
if (j - price[i] < minRem)
minRem = j - price[i];
}
printf("%d\n", minRem);
}
return 0;
}
Input: 3 10000 1 3 9 1 50 5 10 1 2 3 2 1 1 2 3 2 1 50 3 1 2 3 3 0 Output: 4 5 15 3
sum ≤ 1000,n ≤ 1000,約 10⁶ 操作,極快| 錯誤 | 後果 | 修正 |
|---|---|---|
| 不排序 | 大價格先阻擋小價格,答案偏大 | 升序排序 |
| 逆序遍歷 j | 變完全背包,同一菜可重複買 | 正序 5→sum |
| 初始 dp[sum]=0 | 無初始狀態,全不可達 | dp[sum]=1 |
| minRem 初值錯 | 漏記初始餘額 | minRem = sum |
HDU 2546 巧妙地將「餘額 ≥ 5 可透支」轉化為背包可達性問題。核心技巧:排序 + 正序遍歷 + 狀態可達性。這類「特殊約束轉標準模型」的題目,關鍵在於抓住約束的本質邊界(此處為「最後一次購買前必 ≥ 5」),再選擇正確的 DP 遍歷順序保證物品不重複使用。