Prüfer 序列
\(n\) 個點的有標號無根樹可以與一個長度為 \(n - 2\) 的 Prüfer 序列對應。
從樹到 Prüfer 序列
- \(f\) 為空序列。
- 如果當前樹上多於兩個節點,假設當前標號最小的葉子為 \(x\),與 \(x\) 相連的節點標號為 \(y\),那麼把 \(x\) 從樹上刪除,把 \(y\) 加入 \(f\) 末尾。
- 重複 2. 直到樹上只有兩個節點。
性質
- 在構造完 Prufer 序列後原樹中會剩下兩個節點,其中一個一定是編號最大的點 \(n\)。
- 每個節點在序列中出現的次數是其度數減 \(1\),因此沒有出現的就是葉節點。
實現
可以 \(O(n)\) 實現這個轉換。
由於最後一定會留下 \(n\),不妨欽定 \(n\) 為根(把 \(n\) 拎起來)。
定義:
- \(f_i\) 為 \(i\) 的父節點。
- \(d_i\) 為 \(i\) 的兒子數量。
- \(p_i\) 為 prufer 序列的第 \(i\) 項。
流程:
定義 \(i\) 表示當前編號最小的葉子的指標,\(cur\) 為當前序列填到哪一項。
- \(p_{cur} = f_i\)。
- 將 \(d_{p_{cur}}\) 減 \(1\),表示刪去 \(i\) 這個節點。
- 如果 \(d_{p_{cur}} = 0\) 且 \(p_{cur} < i\),那麼 \(p_{cur}\) 比當前最小值還要小,直接令 \(p_{cur + 1} = f_{p_{cur}}\) 。
void Tree_to_Prufer() {
for(int i = 1; i < n; ++ i) {
cin >> f[i];
++ d[f[i]];
}
for(int cur = 0, i = 1; cur < n - 2; ++ i) {
while(d[i]) ++ i;
p[++ cur] = f[i];
while(cur < n - 2 && -- d[p[cur]] == 0 && p[cur] < i) {
p[cur + 1] = f[p[cur]];
++ cur;
}
}
for(int i = 1; i <= n - 2; ++ i) cout << p[i] << ' ';
}
從 Prüfer 序列到樹
- 找到當前不在 \(f\) 中且還未使用的最小元素 \(x\)。
- \(x\) 與 \(f\) 的第一個元素連邊。
- 刪除 \(f\) 的第一個元素,如果 \(f\) 非空,重複上述過程。
- 最後還剩兩個點未使用,將他們連邊。
程式碼實現類似。
void Prufer_to_Tree() {
for(int i = 1; i <= n - 2; ++ i) {
cin >> p[i];
++ d[p[i]]; // 以 $n$ 為根,兒子數量
}
p[n - 1] = n;
for(int cur = 1, i = 1; cur <= n - 1; ++ cur, ++ i) {
while(d[i]) ++ i;
f[i] = p[cur];
while(cur < n - 1 && -- d[p[cur]] == 0 && p[cur] < i) {
f[p[cur]] = p[cur + 1];
++ cur;
}
}
for(int i = 1; i <= n - 1; ++ i) cout << f[i] << ' ';
}
Caylay 定理
\(n\) 個點的有標號無根樹有 \(n^{n -2}\) 種。
例題
P6086 【模板】Prufer 序列
submission
P2290 [HNOI2004] 樹的計數
題意:給定每個點的度數 \(d_i\),求不同的有標號無根樹個數。
相當於 \(d_i - 1\) 個 \(i\) 排進長為 \(n - 1\) 的序列的方案數。
即
\[\dfrac{(n - 1)!}{(d_1 - 1)!(d_2 - 1)!\cdots(d_n - 1)!}
\]
由於保證了答案小於 \(10^{17}\),可以不用高精,全程用一個大於 \(10^{17}\) 的質數取模(如 \(4179340454199820289\))。
特判無解,根據 prufer 序列的性質,一定有 \(\sum d_i - 1 = n - 2\)。
submission