1. 程式人生 > >[HNOI 2004]樹的計數

[HNOI 2004]樹的計數

ack 有一個 water target math 公式 std size eas

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

題解

$Prüfer$編碼&$Cayley$公式。

預備知識:->戳我<-

這裏談下自己的理解:

(此段與題目無關,可選擇跳過)首先對於$Cayley$公式,其實講的就是“$n$階完全圖生成數個數為$n^{n-2}$”,換言之就是“$n$個帶編號頂點的無根生成樹共$n^{n-2}$個”。

證明:這裏引用$Prüfer$編碼,不了解的話可以戳上文鏈接。其實就是對於任何一棵無根生成樹,都有一個長度為$n-2$的序列。這個序列是這樣定義的:每次在葉節點中找到一個編號最小的節點,將其刪去,記錄下相鄰節點。因為是無根,若頂點只有$2$個,顯然只有一棵樹,長度就是$n-2$。

而對於任何一個$Prüfer$編碼都能夠還原成一棵無根樹。

我們回到這道題,我們擁有這樣一個結論:“任何一個$Prüfer$編碼都能夠還原成一棵無根樹”。

那麽我們就可以用$Prüfer$編碼來解決問題。

我們發現第$i$個點會在$Prüfer$編碼中出現$d[i]-1$次:因為自己“被刪”需要$1$個度,他的其他相鄰節點“被刪”要$d[i]-1$個度。

那麽等於說$i$這個數會在編碼中出現$d[i]-1$次。

因為數列長度為$n-2$,我們看有序排列:總共有$(n-2)!$個

考慮去重:因為此時$Prüfer$編碼中的數字$i$恰好出現$d[i]-1$次我們只需要對於每個$i$都除以$(d[i]-1)!$就可以了。

所以答案就是技術分享

註意要特殊討論構成不了樹的情況。

 1 //It is made by Awson on 2017.10.6
 2 #include <map>
 3 #include <set>
 4 #include <cmath>
 5 #include <ctime>
 6 #include <queue>
 7 #include <stack>
 8 #include <vector>
 9 #include <cstdio>
10 #include <string>
11 #include <cstdlib>
12 #include <cstring>
13 #include <iostream>
14 #include <algorithm>
15 #define LL long long
16 #define Max(a, b) ((a) > (b) ? (a) : (b))
17 #define Min(a, b) ((a) < (b) ? (a) : (b))
18 #define sqr(x) ((x)*(x))
19 using namespace std;
20 void read(int &x) {
21   char ch; bool flag = 0;
22   for (ch = getchar(); !isdigit(ch) && ((flag |= (ch == -)) || 1); ch = getchar());
23   for (x = 0; isdigit(ch); x = (x<<1)+(x<<3)+ch-48, ch = getchar());
24   x *= 1-2*flag;
25 }
26 
27 int n, a[155];
28 int cnt[155];
29 int pre[155];
30 
31 void prepare() {
32   bool isprime[155];
33   int q[155], top = 0;
34   memset(isprime, 1, sizeof (isprime));
35   isprime[1] = 0;
36   for (int i = 2; i <= n; i++) {
37     if (isprime[i]) q[++top] = i;
38     for (int j = 1; j <= top && i*q[j] <= n; j++) {
39       pre[i*q[j]] = q[j];
40       isprime[i*q[j]] = 0;
41       if (i%q[j] == 0) break;
42     }
43   }
44 }
45 void noanswer() {
46   printf("0\n");
47   exit(0);
48 }
49 void work() {
50   read(n);
51   prepare();
52   int sum = 0;
53   for (int i = 1; i <= n; i++) {
54     read(a[i]);
55     if (!a[i] && n != 1) noanswer();
56     a[i]--; sum += a[i];
57   }
58   if (sum != n-2) noanswer();
59   for (int i = 2; i <= n-2; i++) {
60     int j = i;
61     while (pre[j]) {
62       cnt[pre[j]]++;
63       j /= pre[j];
64     }
65     cnt[j]++;
66   }
67   for (int i = 1; i <= n; i++)
68     for (int j = 2; j <= a[i]; j++) {
69       int k = j;
70       while (pre[k]) {
71     cnt[pre[k]]--;
72     k /= pre[k];
73       }
74       cnt[k]--;
75     }
76   LL ans = 1;
77   for (int i = 2; i <= n; i++)
78     for (int j = 1; j <= cnt[i]; j++)
79       ans *= i;
80   printf("%lld\n", ans);
81 }
82 int main() {
83   work();
84   return 0;
85 }

[HNOI 2004]樹的計數