【BZOJ 1211】 1211: [HNOI2004]樹的計數 (prufer序列、計數)
阿新 • • 發佈:2018-12-30
1211: [HNOI2004]樹的計數
Time Limit: 10 Sec Memory Limit: 162 MB
Submit: 2468 Solved: 868Description
一個有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
2HINT
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之間連邊,原樹恢復
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