1. 程式人生 > 其它 >圖論專題-學習筆記:Prufer 序列

圖論專題-學習筆記:Prufer 序列

目錄

1. 前言

Prufer 序列,是一種用來描述樹的序列,一般用於一些樹上度數統計的題。

注意作者是 OIer,考慮到 Prufer 序列在 OI 裡面的應用等,本篇文章目前只講述 \(O(n \log n)\) 的求法,\(O(n)\) 的求法後面會跟上來,會鴿子。

2. 詳解

設有一棵 \(n\) 點的樹,則這個 \(n\) 點的樹和一個長度為 \(n-2\) 的 Prufer 序列是一一對應的,下面給出樹與 Prufer 序列的互相轉換方式。

2.1 樹 \(\to\) Prufer 序列

轉換方式如下:

  1. 從當前樹上所有度數為 1 的點中取出編號最小的點,將與其直接連線的邊加入 Prufer 序列中。
  2. 刪除選出的這個點,同時被加入 Prufer 序列的點度數要減一,如果該點度數變為 1 那麼這個點就有可能被取出。
  3. 重複該操作直到只剩下兩個點,此時演算法結束,Prufer 序列長度為 \(n-2\)

使用堆可以很方便的完成這一系列操作,複雜度 \(O(n \log n)\)

這裡放上一張圖,來自參考資料中的 OI - Wiki 中的圖片(不確定他們這張圖是哪來的,反正我從這裡引用過來):

參考程式碼如下,其中 \(fa_i\)

表示 \(i\) 的父親,程式碼中以 \(n\) 為根節點。

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 序列的做法,可以得到如下方式:

  1. 根據 Prufer 序列和上文的一個性質,算出所有點度數。
  2. 列舉 \(i \in [1,n-2]\),設 \(\{p_i\}\) 為 Prufer 序列,每次取出當前未被刪除且度數為一的編號最小的點,設為 \(x\),將 \(x\)\(p_i\) 連邊然後刪去 \(x\),同時減小 \(p_i\) 的度數。
  3. 最後應當會剩下兩個度數為 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 序列和樹是一一對應的,與度數有很大的聯絡。

5. 參考資料