Prufer 序列
Prufer 序列
定義與建立
Prufer 序列可以將一個帶標號 \(n\) 個結點的樹用 \([1,n]\) 中的 \(n-2\) 個整數表示。一個無向帶標號生成樹與數列之間的雙射。
對于一棵樹,每次我們選擇它編號最小的葉子結點,刪除它并記錄下與它相連的節點的編號,那么最終記錄下的 \(n-2\) 個數就組成了這棵樹的 Prufer 序列。
顯然用這個東西維護樹的結構感覺非常不好,這個東西主要用來數數用的。
對樹建立 Prufer 序列
按照定義直接建立,那么每次從堆中取出最小的數,刪去它并記錄。
這樣可以得到一個 \(\mathcal{O(n\log n)}\) 的做法,但是顯然這不夠優美,我們需要一個 \(\mathcal{O(n)}\) 的解法。
我們發現剩余的葉子結點數量是遞減的,每次刪除要么少一個,要么少一個又多一個。
從小到大枚舉編號 \(p\),如果是葉子結點,將和它們相鄰的點放入 Prufer 序列中??紤]這個葉子結點帶來的新葉子結點:
- 如果和它相鄰的點編號 \(p'>p\) 或者 \(p'\) 沒有變成葉子結點,那么不用管,它會在之后被枚舉到。
- 但是如果 \(p'<p\),它在時候不會被枚舉到了。但是發現刪除之前當前最小的葉子結點是 \(p\),所以刪后 \(p'\) 必然是最小的葉子,所以可以直接繼續刪除 \(p'\)。
用 Prufer 還原無根樹
發現 Prufer 序列中的每個數的出現次數就是原樹中每個點的度數 \(-1\),可以每次連一個葉子結點重構。
方法類似,一個暴力的做法是每次選出最小的當前的葉子結點,將它與 Prufer 序列最前面的元素相連,這樣復雜度是 \(\mathcal{O(n\log n)}\) 的。
發現葉子結點編號遞增,設刪去的葉子結點為 \(p\),和它相鄰的是 \(p'\)。當加完 \(p\) 后 \(p'\) 的度數變為 \(1\) 并且 \(p'<p\),那么它下一次一定被選擇,直接繼續連邊即可。
for(int i=1;i<=n-2;i++) pru[i]=rd(),ind[pru[i]]++; for(int i=1;i<=n;i++) if(!ind[i]) exist[i]=true; int pos=1; for(int i=1,p;i<=n;i++) {if(!exist[i]) continue;p=i;while(p<=i){if(pos==n-1) { fa[p]=n; break; }fa[p]=pru[pos],p=pru[pos],ind[p]--,pos++;if(pos>=n) break;if(ind[p] || p>i) break;}if(!ind[p]) exist[p]=true; } for(int i=1;i<n;i++) ret^=1ll*i*fa[i]; printf("%lld\n",ret);主要性質
-
Prufer 序列與無根樹一一對應(好像比較顯然)
-
度數為 \(d_i\) 的點在 Prufer 序列中出現 \(d_i-1\) 次(上面還用到了這個結論)
-
【根據度數求方案】對于給定每個點度數為 \(d_i\) 的無根樹,方案數為:
\[\dfrac{(n-2)!}{\prod_{i=1}^{n}(d_i-1)!} \]證明:Prufer 序列長度為 \(n-2\),每個數出現次數為 \(d_i-1\),根據組合意義直接計算全排列即可。
-
【根據連通塊數量與大小求方案】一個 \(n\) 個點 \(m\) 條邊的帶標號無向圖有 \(k\) 個連通塊,每個連通塊大小為 \(s_i\),需要增加 \(k-1\) 條邊使得整個圖聯通,方案數為:(但是當 \(k=1\) 時需要特判)
\[n^{k-2}\cdot\prod_{i=1}^{k}s_i \]證明見 OI-wiki
總結
- 上一篇: 剑灵活力没了怎么办 剑灵活力值的解决方法
- 下一篇: 数数题(计数类 DP)做题记录