來源:ACM/ICPC 區域賽 / POJ / ZOJ — C - Hyperhuffman
任務:給定 N (≤ 500,000) 個字符的出現次數 Pi(已按非遞增排序),計算哈夫曼編碼後的總位元長度。
| 標準哈夫曼 | Hyperhuffman (本題) |
|---|---|
| 合併代價 = w1 + w2 | 合併代價 = w1 + w2 + 原有代價累加 |
| 節點權值 = 頻率和 | 節點結構體含 sum(累積代價) + v(頻率和) |
| 最終答案 = Σ freq × depth | 最終答案 = 根節點 sum |
每次合併兩最小節點 p1, p2:
v = p1.v + p2.vsum = p1.sum + p2.sum + p1.v + p2.v因為新節點深度比兩子樹各深 1,所以所有葉子編碼長度 +1,總長度增加 p1.v + p2.v。遞歸累加即得 sum。
P_i 作為初始節點 (v=P_i, sum=0) 推入 最小堆(優先隊列,按 v 排序)。p1, p2 → 合併生成 p → 推回堆。sum 即為答案。#include
using namespace std;
using ll = long long;
struct Node {
ll sum = 0; // 累積代價
ll v = 0; // 頻率和
bool operator<(const Node& o) const { return v > o.v; } // 最小堆
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
while (cin >> n) {
priority_queue pq;
for (int i = 0; i < n; ++i) {
Node nd; cin >> nd.v; pq.push(nd);
}
while (pq.size() > 1) {
Node p1 = pq.top(); pq.pop();
Node p2 = pq.top(); pq.pop();
Node p;
p.v = p1.v + p2.v;
p.sum = p1.sum + p2.sum + p1.v + p2.v;
pq.push(p);
}
cout << pq.top().sum << '\n';
}
return 0;
}
| 指標 | 複雜度 | 說明 |
|---|---|---|
| 時間 | O(N log N) | N 次 push/pop,每次 log N |
| 空間 | O(N) | 優先隊列存 N 個節點 |
| 數據類型 | 64-bit (long long) | P_i ≤ 1e9, N ≤ 5e5, 答案可達 ~1e19 |
輸入 Pi 已非遞增,可用 雙隊列法 將 O(N log N) 降為 O(N):
此法利用「合併產生的頻率單調遞增」性質,避免堆操作,極大提速。
輸入:
3
1 1 4
過程:
初始: (1,0) (1,0) (4,0)
合併 1+1: v=2, sum=0+0+1+1=2 → 堆: (2,2) (4,0)
合併 2+4: v=6, sum=2+0+2+4=8 → 堆: (6,8)
答案: 8 ✓
long long / int64_t。while(cin>>n) 循環讀取。priority_queue 默認大頂堆,需 operator< 反向或用 greater<>。Hyperhuffman 在標準哈夫曼基礎上引入累積代價字段,將「葉子深度 × 頻率」的總和轉化為合併過程中的增量累加。結合已排序輸入的雙隊列 O(N) 優化,是大規模字符集編碼長度計算的標準解法。