1. 程式人生 > 實用技巧 >Prufer序列性質&證明

Prufer序列性質&證明

前言

下午HHY還有AAK看到了這個
質問我Prufer序列是啥
被迫複習一波

引入

直接從題目看吧
[HNOI2004]樹的計數
大概意思就是給你n個節點
告訴你每個節點的度數
然後問你根據這些度數能夠生成多少棵樹
看樣例

4  
2 1 2 1 

畫個圖解釋一下


題目中給出的樣例只有這兩種情況,所以輸出答案為2
我們更關心答案怎麼來的,下面來講一下\(Prufer\)序列

Prufer序列

性質一

  • 存在無根樹轉為\(Prufer\)序列以及\(Prufer\)序列轉為無根樹兩種操作,換言之,上述兩者是互射的(可以互相轉化)

證明一

  • 無根樹轉\(Prufer\)

    • 找到編號最小且度數為1的點
    • 刪除該節點,並且在序列中新增與該節點連線的節點
    • 重複1、2操作,直至樹上只剩下兩個點
  • Prufer轉無根樹

    • \(Prufer\)序列為集合\(M\),另一個集合 \(G { 1,2,3…n }\)
    • 每次提取M中最靠前的元素u與G中不存在與M且最靠前的元素v,將u與v連邊,分別在兩個集合中刪除u、v。
    • 最後將G中剩下的兩個元素連邊

舉個栗子
看上面的第一個圖

圖轉\(Prufer\)序列
先找到2,刪除2和2->1連邊,將1入列,刪除1和1->3連邊,3入列,序列就是“1,3”

\(Prufer\)序列轉圖
取出M中的1和G中的2連邊,分別刪除兩個集合中的元素
取出M中的3和G中的1連邊,然後……同上……
此時,M空了,G中只剩下了3,4,連線3,4就行了

性質二

\(Prufer\)序列是一種對有標號無根樹的編碼,長度為節點數-2

證明二

看證明一當中轉換的要求
直至樹上只剩下兩個點
可以看到最後有兩個點直接忽略
因為此時再判斷順序沒有意義
那就是總數-2

性質三

對於給定的n個點度數,可以構造的樹的數量為
$ (n-2)!/((d1-1)!×(d2-1)!×…×(dn-1)!) $

證明三

需要一丟丟前置知識

  • 因為每個點的度數為d,在構造序列的時候
    我們會發現,每有一個度數就會入序列一次
    但是還要留一次給刪除操作,就不入序列了
    所以對於度數為\(d_i\)的點i
    入序列的次數為\(d_i-1\)
  • 由性質一可知序列和圖之間是一一對應關係
    所以說n個點的序列長度為n-2
    其全排列為\((n-2)!\)
  • 但是考慮到在序列中會有好多重複出現的點
    比如1,1,2
    按照位置全排列\(A_3^3\)有6種
    但是實際上只有1,1,2 1,2,1 2,1,1一共3種
    只需要\(\frac {A_3^3} {(d_1-1)!}\)就是正確的不重複的樹的數量
    於是乎,上述結論被證明

    對於給定的n個點度數,可以構造的樹的數量為

\[(n-2)!/((d1-1)!×(d2-1)!×…×(dn-1)!) \]

程式碼實現

對於這個題目,需要判斷幾個地方
當轉換prufer序列的時候,如果入列次數!=n-2,就一定有問題,輸出0
還有,如果有的節點度數為0,那圖就不聯通,那就輸出0
然後對於個數的求解
組合數打個表就可以了

Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int maxn=155;
int c[maxn][maxn];
int ans,d[maxn];
int sum;
int n;

inline void pre(){
	for(int i=0;i<=n;i++){
		c[i][0]=1;
		for(int j=1;j<=i;j++)
			c[i][j]=c[i-1][j]+c[i-1][j-1];
	}
}

int main(){
	cin>>n;
	if(n==1){
		cin>>d[1];
		if(d[1]==0) cout<<1<<endl;
		else cout<<0<<endl;
		return 0;
	}
	pre();
	for(int i=1;i<=n;i++){
		cin>>d[i];
		if(!d[i]) return cout<<0<<endl,0;
		d[i]--;
		sum+=d[i];
	}
	if(sum!=n-2) return cout<<0<<endl,0;
	sum=0,ans=1;
	for(int i=1;i<=n;i++)
		ans*=c[n-2-sum][d[i]],sum+=d[i];
	cout<<ans<<endl;
	return 0;
}

總結

沒啥特別難得地方
就是性質三不太好理解
需要多找幾個例子
講真從去年到現在用的並不多
看過就當做是一個小知識拓展就好
蟹蟹~