1. 程式人生 > >【BZOJ 1211】 1211: [HNOI2004]樹的計數 (prufer序列、計數)

【BZOJ 1211】 1211: [HNOI2004]樹的計數 (prufer序列、計數)

1211: [HNOI2004]樹的計數

Time Limit: 10 Sec  Memory Limit: 162 MB
Submit: 2468  Solved: 868

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

Source

【分析】

  無根樹的表示法用prufer數列。【長姿勢】

將樹轉化成Prufer數列的方法

一種生成Prufer序列的方法是迭代刪點,直到原圖僅剩兩個點。對於一棵頂點已經經過編號的樹T,頂點的編號為{1,2,...,n},在第i步時,移去所有葉子節點(度為1的頂點)中標號最小的頂點和相連的邊,並把與它相鄰的點的編號加入Prufer序列中,重複以上步驟直到原圖僅剩2個頂點。 例子
Prufer數列 以上面的樹為例子,首先在所有葉子節點中編號最小的點是2,和它相鄰的點的編號是3,將3加入序列並刪除編號為2的點。接下來刪除的點是4,5被加入序列,然後刪除5,1被加入序列,1被刪除,3被加入序列,此時原圖僅剩兩個點(即3和6),Prufer序列構建完成,為{3,5,1,3}

將Prufer數列轉化成樹的方法

設{a1,a2,..an-2}為一棵有n個節點的樹的Prufer序列,另建一個集合G含有元素{1..n},找出集合中最小的未在Prufer序列中出現過的數,將該點與Prufer序列中首項連一條邊,並將該點和Prufer序列首項刪除,重複操作n-2次,將集合中剩餘的兩個點之間連邊即可。 例子 仍為上面的樹,Prufer序列為{3,5,1,3},開始時G={1,2,3,4,5,6},未出現的編號最小的點是2,將2和3連邊,並刪去Prufer序列首項和G中的2。接下來連的邊為{4,5},{1,5},{1,3},此時集合G中僅剩3和6,在3和6之間連邊,原樹恢復
  說明樹和prufer數列是一一對應的。   這個數列的特點是:這個點的度數-1=它在數列的出現次數。   所以數列總長度是n-2。   這道是prufer數列的裸題。   首先判斷是否無解啦。   特判n=1,然後d=0,d>=n這種情況。   且$\sum d[i]-1 = n-2$要成立。   然後最後的答案就是$\dfrac{(n-2)!}{\Pi (d[i]-1)!}$   手動消因子即可。最後答案保證不超過long long了。
 1 #include<cstdio>
 2 #include<cstdlib>
 3 #include<cstring>
 4 #include<iostream>
 5 #include<algorithm>
 6 using namespace std;
 7 #define Maxn 160
 8 #define LL long long
 9 
10 int d[Maxn],cnt[Maxn];
11 
12 void cal(int x,int y)
13 {
14     for(int i=2;i*i<=x;i++) if(x%i==0)
15     {
16         while(x%i==0) cnt[i]+=y,x/=i;
17     }
18     if(x!=1) cnt[x]+=y;
19 }
20 
21 int main()
22 {
23     int n;
24     scanf("%d",&n);
25     for(int i=1;i<=n;i++) scanf("%d",&d[i]);
26     if(n==1)
27     {
28         if(d[1]==0) printf("1\n");
29         else printf("0\n");
30     }
31     else
32     {
33         int sum=0;
34         for(int i=1;i<=n;i++)
35         {
36             if(d[i]==0||d[i]>=n) {printf("0\n");return 0;}
37             sum+=(--d[i]);
38         }
39         if(sum!=n-2) printf("0\n");
40         else
41         {
42             for(int i=1;i<=n;i++) cnt[i]=0; 
43             for(int i=2;i<=n-2;i++) cal(i,1);
44             for(int i=1;i<=n;i++) for(int j=2;j<=d[i];j++) cal(j,-1);
45             LL ans=1;
46             for(int i=1;i<=n;i++) while(cnt[i]--) ans=1LL*ans*i;
47             printf("%lld\n",ans);
48         }
49     }
50     return 0;
51 }
View Code