1. 程式人生 > 實用技巧 >學習筆記:Prufer 編碼

學習筆記:Prufer 編碼

Prufer 編碼可以將無根樹與序列之間進行轉化。


一個 \(n\) 個點、區分編號的無向圖 和 Prufer 序列一定是一一對應的,下面會給出對映方式。

藉此可以證明 Cayley 定理: \(n\) 個點的無根、區分編號生成樹個數為 \(n ^ {n-2}\)

無根樹轉序列

設一棵 \(n\) 個節點的無根樹,寫出轉化為 Prufer 序列的步驟:

  1. 找到編號最小的葉節點 \(x\),把 \(x\) 相鄰的點加入序列,然後,刪掉 \(x\)
  2. 當點數 \(= 2\) 時,終止(若不終止這次輸出的一定是 \(n\) 所以是確定資訊 # 無區分資訊),否則繼續迭代。

所以 Prufer 序列長度為 \(n - 2\)

依此我們可以看出一個性質

考慮 \(x\) 在 Prufer 序列中出現的次數 + 1: \(cnt_x + 1\)\(x\) 的度數


顯然可以 \(O(n \log n)\) 用堆做。

也有線性 \(O(n)\) 的做法:

  1. 從小到大列舉哪個選擇的點 \(j\)

  2. 每次刪除,至多會多出來一個待選葉子點 \(x\)

    • 若沒有多出來葉子點 / \(x > j\),那麼繼續增大 \(j\)

    • 否則,遞迴繼續刪除 \(x\) 即可。

序列轉無根樹

步驟:

考慮 \(x\) 在序列中出現的次數 + 1: \(cnt_x + 1\)\(x\) 的度數,所有 \(cnt_x = 1\)

\(x\) 加入備選集合。

  1. 按順序考慮序列每一項 \(x\)
  2. 從備選集合中找到編號最小的 \(y\),連邊 \((x, y)\)
  3. 減少 \(cnt_x \gets cnt_x - 1\),若減到 \(1\),把 \(x\) 加入備選集合。

複雜度也可以做到 \(O(n)\),和上述方法類似。

想法

後來突然有一個疑問:為什麼轉化後的 Prufer 是唯一的呢?

後來百思不得其解後發現自己鑽牛角尖了,如上兩步,我們給出了:

  • Prufer 序列轉為唯一確定形態無根樹的方法
  • 無根樹轉化為唯一確定 Prufer 序列的方法

那麼無根樹和 Prufer 序列就是一一對應的了。

模板題

AcWing 2419. prufer序列

注意,這裡規定了 \(n\) 為根,那麼顯然更方便了一點,一定是從葉子往上刪:

  • \(cnt_x\)\(x\) 的兒子數
  • \(cnt_x = 0\) 加入備選集合

就可以了。

#include <iostream>
#include <cstdio>

using namespace std;

const int N = 100005;

int n, m, f[N], p[N], d[N];

void inline fToP() {
	for (int i = 1; i < n; i++) d[f[i]]++;
	for (int i = 1, j = 1; i <= n - 2; j++) {
		while (d[j]) j++;
		p[i++] = f[j];
		while (i <= n - 2 && --d[p[i - 1]] == 0 && p[i - 1] < j) p[i++] = f[p[i - 1]];
	}
}

void inline pToF() {
    for (int i = 1; i <= n - 2; i++) d[p[i]]++;
    p[n - 1] = n;
    for (int i = 1, j = 1; i < n; i++, j++) {
    	while (d[j]) j++;
    	f[j] = p[i];
    	while (i < n - 1 && --d[p[i]] == 0 && p[i] < j) f[p[i]] = p[i + 1], ++i;
    }
}

int main() {
    scanf("%d%d", &n, &m);
    if (m == 1) {
        for (int i = 1; i < n; i++) scanf("%d", f + i);
        fToP();
    	for (int i = 1; i <= n - 2; i++) printf("%d ", p[i]);
    } else {
        for (int i = 1; i <= n - 2; i++) scanf("%d", p + i);
        pToF();
        for (int i = 1; i < n; i++) printf("%d ", f[i]);
    }
    return 0;
}