【BZOJ 1211】 1211: [HNOI2004]树的计数 (prufer序列、计数)
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之间连边,原树恢复
说明树和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 }