[HNOI 2004]樹的計數
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
42 1 2 1
Sample Output
題解
$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]樹的計數