經典動態規劃問題:給定 n 種磚塊(每種無限供應),每塊長寬高可任意旋轉(6 種朝向),上層磚塊長寬必嚴格小於下層,求最大疊塔高度。本文詳細講解「狀態展開 + 排序 + DP」標準解法,並提供優化代碼模板。
dp[i] 表示以第 i 個實例作為塔底(最下層)時的最大高度。a[i].l > a[j].l && a[i].w > a[j].w,則 dp[i] = max(dp[i], a[i].h + dp[j])。max(dp[i])。#includeusing namespace std; struct Brick { int l, w, h; // 長 ≥ 寬 }; bool cmp(const Brick &a, const Brick &b) { if (a.l != b.l) return a.l > b.l; // 長遞減 return a.w > b.w; // 寬遞減 } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, tc = 1; while (cin >> n && n) { vector a; a.reserve(n * 3); for (int i = 0; i < n; ++i) { int x, y, z; cin >> x >> y >> z; int b[3] = {x, y, z}; sort(b, b + 3); // b[0] ≤ b[1] ≤ b[2] // 3 種有效朝向:長≥寬 a.push_back({b[2], b[1], b[0]}); // 最大作長,中作寬,最小作高 a.push_back({b[2], b[0], b[1]}); // 最大作長,最小作寬,中作高 a.push_back({b[1], b[0], b[2]}); // 中作長,最小作寬,最大作高 } sort(a.begin(), a.end(), cmp); int m = a.size(); vector dp(m); for (int i = 0; i < m; ++i) dp[i] = a[i].h; // 初始:自身高度 // 從後向前 DP:i 為塔底,尋找可放在其上的 j for (int i = m - 2; i >= 0; --i) { for (int j = i + 1; j < m; ++j) { if (a[i].l > a[j].l && a[i].w > a[j].w) { dp[i] = max(dp[i], a[i].h + dp[j]); } } } int ans = *max_element(dp.begin(), dp.end()); cout << "Case " << tc++ << ": maximum height = " << ans << '\n'; } return 0; }
| 環節 | 細節 | 理由 |
|---|---|---|
| 去重朝向 | 長≥寬,僅保留 3 種 | 6 種旋轉中 (l,w) 與 (w,l) 等價,固定長≥寬消除對稱性 |
| 排序順序 | 長遞減、寬遞減 | 保證下層在前,上層在後,DP 單向轉移無環 |
| DP 方向 | 從後向前(塔底→塔頂) | 塔底依賴上層結果,逆序計算 dp[j] 已知 |
| 初始值 | dp[i] = a[i].h | 至少可單獨作為塔 |
| 嚴格小於 | l > l' && w > w' | 題目要求「留空間給猴子踩」,不可等於 |
完全滿足 1000ms/32MB 限制。
dp[i] = max(dp[i], dp[j] + a[i].h) 而非 a[j].h。若每種磚塊只有有限數量,需將展開的實例標記來源類型,DP 狀態增加「已用數量」維度,轉為多重背包或費用流模型,複雜度顯著上升。
HDU 1069 是「盒子嵌套 / 疊塔」問題的經典入門。核心技巧:狀態展開消除旋轉對稱性 → 排序構建偏序 → DP 求最優路徑。掌握此模板,可秒殺 POJ 1017、UVA 437 等同類題目。