圖論專題-學習筆記:Prufer 序列
阿新 • • 發佈:2022-04-17
目錄
表示 \(i\) 的父親,程式碼中以 \(n\) 為根節點。
1. 前言
Prufer 序列,是一種用來描述樹的序列,一般用於一些樹上度數統計的題。
注意作者是 OIer,考慮到 Prufer 序列在 OI 裡面的應用等,本篇文章目前只講述 \(O(n \log n)\) 的求法,\(O(n)\) 的求法後面會跟上來,會鴿子。
2. 詳解
設有一棵 \(n\) 點的樹,則這個 \(n\) 點的樹和一個長度為 \(n-2\) 的 Prufer 序列是一一對應的,下面給出樹與 Prufer 序列的互相轉換方式。
2.1 樹 \(\to\) Prufer 序列
轉換方式如下:
- 從當前樹上所有度數為 1 的點中取出編號最小的點,將與其直接連線的邊加入 Prufer 序列中。
- 刪除選出的這個點,同時被加入 Prufer 序列的點度數要減一,如果該點度數變為 1 那麼這個點就有可能被取出。
- 重複該操作直到只剩下兩個點,此時演算法結束,Prufer 序列長度為 \(n-2\)。
使用堆可以很方便的完成這一系列操作,複雜度 \(O(n \log n)\)。
這裡放上一張圖,來自參考資料中的 OI - Wiki 中的圖片(不確定他們這張圖是哪來的,反正我從這裡引用過來):
參考程式碼如下,其中 \(fa_i\)
void TreeToPrufer() { for (int i = 1; i < n; ++i) fa[i] = Read(), ++d[i], ++d[fa[i]]; priority_queue <int> q; for (int i = 1; i <= n; ++i) if (d[i] == 1) q.push(-i); for (int i = 1; i <= n - 2; ++i) { int x = -q.top(); q.pop(); Prufer[++Prufer[0]] = fa[x]; --d[fa[x]]; if (d[fa[x]] == 1) q.push(-fa[x]); } }
實際上,根據構造方式,我們可以得出 Prufer 序列的兩個性質:
- \(i\) 號點在 Prufer 序列中出現的次數為 \(i\) 號點度數 - 1。
同時,構造完 Prufer 序列後原樹只會剩下兩個節點,其中一個一定是 \(n\)。
2.2 Prufer 序列 \(\to\) 樹
仿照樹 \(\to\) Prufer 序列的做法,可以得到如下方式:
- 根據 Prufer 序列和上文的一個性質,算出所有點度數。
- 列舉 \(i \in [1,n-2]\),設 \(\{p_i\}\) 為 Prufer 序列,每次取出當前未被刪除且度數為一的編號最小的點,設為 \(x\),將 \(x\) 與 \(p_i\) 連邊然後刪去 \(x\),同時減小 \(p_i\) 的度數。
- 最後應當會剩下兩個度數為 1 的點,其中一個一定是 \(n\),將這兩個點連起來即可。
照樣使用堆來實現這一過程,複雜度 \(O(n \log n)\)。
參考程式碼如下,這裡採用了鏈式前向星連邊,然後使用一遍 dfs 求出 \(fa_i\)。
void dfs(int now, int father)
{
fa[now] = father;
for (int i = Head[now]; i; i = Edge[i].Next)
{
int u = Edge[i].To;
if (u == father) continue ;
dfs(u, now);
}
}
void PruferToTree()
{
for (int i = 1; i <= n - 2; ++i) Prufer[i] = Read(), ++d[Prufer[i]];
for (int i = 1; i <= n; ++i) ++d[i];
priority_queue <int> q;
for (int i = 1; i <= n; ++i) if (d[i] == 1) q.push(-i);
for (int i = 1; i <= n - 2; ++i)
{
int x = -q.top(); q.pop();
add_Edge(x, Prufer[i]); add_Edge(Prufer[i], x);
--d[Prufer[i]]; if (d[Prufer[i]] == 1) q.push(-Prufer[i]);
}
int p1 = -q.top(); q.pop();
add_Edge(p1, n); add_Edge(n, p1);
dfs(n, n);
}
3. 性質
首先前面提到過一個性質(設 \(i\) 號點度數為 \(d_i\)):
- \(i\) 號點在 Prufer 序列中出現的次數為 \(d_i-1\)。
然後利用 Prufer 序列,我們可以證明:
- \(n\) 個點無標號無根樹形態有 \(n^{n-2}\) 種。
以及一些需要度數的題都可以用 Prufer 序列求解。
這裡再放上一個性質:對於給定 \(d\) 序列,\(n\) 個點有標號無根樹的種類有:
\[\dbinom{n-2}{d_1-1,d_2-1,...,d_n-1}=\dfrac{(n-2)!}{(d_1-1)!(d_2-1)!...(d_n-1)!} \]順帶一提,如果利用 Prufer 序列來造資料,隨機情況下樹高是 \(\sqrt{n}\) 而不是 \(\log n\) 的。
4. 總結
Prufer 序列和樹是一一對應的,與度數有很大的聯絡。