無根樹的計數——prufer序列


參考博客https://www.cnblogs.com/dirge/p/5503289.html

(1)prufer數列是一種無根樹的編碼表示,類似於hash。

一棵n個節點帶編號的無根樹,對應唯一串長度為n-2的prufer編碼。所以一個n階完全圖的生成樹個數就是n^{n-2}

首先定義無根樹中度數為1的節點是葉子節點。

找到編號最小的葉子並刪除,序列中添加與之相連的節點編號,重復執行直到只剩下2個節點。

 

(2)prufer序列轉化為無根樹。

我們設點集為{1,2...n}。然后我們每次找到點集中沒有出現在prufer序列中的最小的點(這一定是這個時刻刪除的葉子節點),然后再取出prufer序列中的第一個元素,兩個點建邊,在將兩個點在分別刪除。

 

重要性質:prufer序列中某個編號出現的次數就是這個編號節點的度數-1。

 

很多時候,無根樹樹的問題都可以轉化為求解prufer序列的問題,從而大大簡化了問題。

 

下面提供了三道例題。

 

例題:

BSOJ 2503 -- 【HNOI2004】樹的計數

Description

  一個有n個結點的樹,設它的結點分別為v1, v2, …, vn,已知第i個結點vi的度數為di,問滿足這樣的條件的不同的樹有多少棵。 
  給定n,d1, d2, …, dn,編程需要輸出滿足d(vi)=di的樹的個數。

Input

  第一行是一個正整數n,表示樹有n個結點。第二行有n個數,第i個數表示di,即樹的第i個結點的度數。其中1<=n<=150,輸入數據保證滿足條件的樹不超過10^17個。

Output

  輸出滿足條件的樹有多少棵。

Sample Input

4 2 1 2 1

Sample Output

2

Hint

  

根據“prufer序列中某個編號出現的次數就是這個編號節點的度數-1”這一性質,這道題就變成了給定元素個數的排列問題。

ans=\frac{(n-2)!}{\prod d_{i}-1}

代碼:
 

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<ctime>
#define ll long long

using namespace std;
inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}

int n;
ll fac[155];
ll ksm(ll t,ll x) {
	ll ans=1;
	for(;x;x>>=1,t=t*t)
		if(x&1) ans*=t;
	return ans;
}
bool vis[150];
ll pri[150],cnt[150];
void pre(int n) {
	for(int i=2;i<=n;i++) {
		if(!vis[i]) pri[++pri[0]]=i;
		for(int j=1;j<=pri[0]&&i*pri[j]<=n;j++) {
			vis[i*pri[j]]=1;
			if(i%pri[j]==0) break;
		}
	}
}
void work(ll n,int flag) {
	for(int i=1;i<=pri[0];i++) {
		for(int j=pri[i];j<=n;j*=pri[i]) {
			cnt[i]+=flag*n/j;
		}
	}
}
int tot;
ll ans[5000];
void Cheng(int t) {
	for(int i=1;i<=ans[0];i++) ans[i]*=t;
	for(int i=1;i<=ans[0];i++) {
		ans[i+1]+=ans[i]/10;
		ans[i]%=10;
	}
	while(ans[ans[0]+1]) {
		ans[0]++;
		ans[ans[0]+1]+=ans[ans[0]]/10;
		ans[ans[0]]%=10;
	}
}

int main() {
	n=Get();
	pre(n);
	work(n-2,1);
	int a;
	for(int i=1;i<=n;i++) {
		a=Get();
		if(!a&&n>1) {cout<<0;return 0;}
		if(a>1) work(a-1,-1);
		tot+=a;
	}
	if(tot!=(n-1)*2) return cout<<0,0;
	if(n==1) {
		if(tot) cout<<0;
		else cout<<1;
		return 0;
	}
	if(n==2) return cout<<1,0;
	ans[0]=1;
	ans[1]=1;
	for(int i=1;i<=pri[0];i++) 
		for(int j=1;j<=cnt[i];j++) Cheng(pri[i]);
	for(int i=ans[0];i>=1;i--) cout<<ans[i];
	return 0;
}

 

BSOJ 5553 -- 【模擬試題】wangyurzee的樹

Description

wangyurzee有n個各不相同的節點,編號從1到n。wangyurzee想在它們之間連n-1條邊,從而使它們成為一棵樹。 
可是wangyurzee發現方案數太多了,於是他又給出了m個限制條件,其中第i個限制條件限制了編號為u[i]的節點的度數不能為d[i]。 一個節點的度數,就是指和該節點相關聯的邊的條數。 這樣一來,方案數就減少了,問題也就變得容易了,現在請你告訴wangyurzee連邊的方案總數為多少。 答案請對1000000007取模。

Input

第一行輸入2個整數n(1<=n<=1000000),m(0<=m<=17)分別表示節點個數以及限制個數。 
第2行到第m+1行描述m個限制條件,第i+1行為2個整數u[i],d[i],表示編號為u[i]的節點度數不能為d[i]。 
為了方便起見,保證1<=ui<=m。同時保證1<=ui<=n,1<=di<=n-1,保證不會有兩條完全相同的限制。

Output

輸出一行一個整數表示答案。

Sample Input

3 1 1 2

Sample Output

2

顯然要容斥。我們就用{總的方案數}-{不滿足一個條件的方案數}+{不滿足兩個條件的方案數}-...

注意有個坑點,就是如果我們枚舉的兩個條件的u相同,那么方案數直接為0。

然后就是如何計數的問題。假設我們已經定了k個點的度數d[1],d[2]...d[k]。設res=n-2-\sum _{1<=i<=k}d[i]-1。那么方案數就是C_{n-2}^{d[1]-1}\cdot C_{n-2-(d[1]-1)}^{d[2]-1}...\cdot C_{tot+d[k]-1}^{d[k]-1}\cdot (n-k)^{res}

代碼:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<ctime>
#define ll long long 
#define N 1000005
#define mod 1000000007ll

using namespace std;
inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}

int n,m;
int u[20],d[20];
vector<int>st[N];
ll fac[N],ans;
ll ksm(ll t,ll x) {
	ll ans=1;
	for(;x;x>>=1,t=t*t%mod)
		if(x&1) ans=ans*t%mod;
	return ans;
}
ll C(int n,int m) {return fac[n]*ksm(fac[m],mod-2)%mod*ksm(fac[n-m],mod-2)%mod;}
bool vis[N];
void dfs(int v,int flag,int n,int res,ll tot) {
	if(v>m) {
		(ans+=flag*tot*ksm(res,n)%mod+mod)%=mod;
		return ;
	}
	dfs(v+1,flag,n,res,tot);
	if(n>=d[v]&&!vis[u[v]]) {
		vis[u[v]]=1;
		dfs(v+1,-flag,n-d[v],res-1,tot*C(n,d[v])%mod);
		vis[u[v]]=0;
	}
}
int main() {
	n=Get(),m=Get();
	int a;
	for(int i=1;i<=m;i++) {
		u[i]=Get();d[i]=Get()-1;
	}
	fac[0]=1;
	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
	dfs(1,1,n-2,n,1);
	cout<<ans;
	return 0;
}

 

BSOJ 5445 -- 【2018雅禮】樹

Description

  有n個點,第i個點的限制為度數不能超過ai。
  現在對於每一個s(1<=s<=n),問從這n個點中選出s個點組成有標號無根樹的方案數。

Input

  第一行一個整數表示n。
  第二行n個整數a1~an。

Output

  輸出僅一行n個整數,第i個整數表示s=i時的答案。

Sample Input

3 2 2 1

Sample Output

3 3 2

Hint

【數據范圍】
  對於20%的數據,n≤6。
  對於60%的數據,n≤50。
  對於100%的數據,n≤100。

我們說過,處理無根樹計數的問題可以轉化為prufer序列的計數問題。

我們設f[i][j]長度為i的prufer序列,用了j個點的方案數。考慮新增一個點v,我們假設它的度數為k(1<=k<=a[v]),然后就可以得到轉移方程f[i+k-1][j+1]+=f[i][j]\cdot C_{i+k-1}^{k-1}

最后特判s=1時,答案是n,s>=2是,答案是f[s-2][s]

代碼:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<ctime>
#define ll long long
#define mod 1004535809ll
#define N 105

using namespace std;
inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}

int n;
ll f[N][N],g[N][N],c[N][N],w[N];
int main() {
	n=Get();
	c[0][0]=1;
	for(int i=1;i<=n;i++)
		for(int j=0;j<=i;j++)
			c[i][j]=(!j||i==j)?1:(c[i-1][j-1]+c[i-1][j])%mod;
	f[0][0]=1;
	for(int i=1;i<=n;i++) {
		w[i]=Get();
		memcpy(g,f,sizeof(f));
		for(int j=0;j<n;j++) {
			for(int k=0;k<n;k++) {
				if(!f[j][k]) continue ;
				for(int q=0;q<w[i]&&q+j<=n-2;q++) {
					(g[q+j][k+1]+=f[j][k]*c[q+j][q]%mod)%=mod;
				}
			}
		}
		memcpy(f,g,sizeof(f));
	}
	cout<<n<<" ";
	for(int i=2;i<=n;i++) cout<<f[i-2][i]<<" ";
	return 0;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM