bzoj1211: [HNOI2004]树的计数(prufer序列+组合数学)
1211: [HNOI2004]树的计数
题目:传送门
题解:
今天刚学prufer序列,先打几道简单题
首先我们知道prufer序列和一颗无根树是一一对应的,那么对于任意一个节点,假设这个节点的度数为k,那么在prufer序列里面这个节点就会出现k-1次
(反过来也同理成立)
那么具体的原因这里有解释:
对于任意一个节点在prufer序列里出现一次的话,那么就表示我有一个孩子被删了,那么少了的一次去哪里了呢,因为每次加进去的都是父亲节点,那么少的肯定就是我自己连出去的一条边啊...
知道了这个推论之后,这道题就很简单了:
题目要求的树必须满足度数的要求,那只要这棵树的prufer序列满足度数要求就ok了啊...
这样我们就可以用组合数学,直接根据给出的d数组做。
很容易想到:ans=(n-2)!/(d1-1)!*(d2-1)!....(dn-1)! (如果是入度小于二的话不用计算)
刚开始傻逼比的打全排列...有重复啊啊啊啊!!!
最后一点:题目保证方案数不会超过10^17,那long long 肯定没问题啊...可是我们求得是组合,是有除法的(也就是说乘法的时候还是会爆)....ORT那就质因数分解咯...
代码:
1 #include<cstdio> 2 #include<cstring> 3 #include<cstdlib> 4 #include<cmath> 5 #include<algorithm> 6 typedef long long LL; 7 using namespace std; 8 int n; 9 LL d[210],pr[210]; 10 int s[210]; 11 bool pd(LL x) 12 { 13 double t=sqrt(double(x+1)); 14 for(int i=2;i<=t;i++) 15 if(x%i==0) 16 return false; 17 return true; 18 } 19 LL p_m(LL a,int b) 20 { 21 LL ans=1; 22 while(b!=0) 23 { 24 if(b%2==1)ans*=a; 25 b/=2;a*=a; 26 } 27 return ans; 28 } 29 int main() 30 { 31 scanf("%d",&n);int sum=0; 32 for(int i=1;i<=n;i++){scanf("%d",&d[i]);sum+=d[i];} 33 if(n==1 && d[1]!=0){printf("0\n");return 0;} 34 if(n>1)for(int i=1;i<=n;i++){if(d[i]==0){printf("0\n");return 0;}} 35 if(sum-n!=n-2){printf("0\n");return 0;} 36 int len=0; 37 for(LL i=2;i<=150;i++)if(pd(i)==true)pr[++len]=i; 38 memset(s,0,sizeof(s)); 39 for(int i=1;i<=n-2;i++) 40 { 41 int x=i; 42 for(int j=1;j<=len;j++) 43 while(x%pr[j]==0 && x!=0) 44 {s[j]++;x/=pr[j];} 45 } 46 for(int i=1;i<=n;i++) 47 for(int k=1;k<=d[i]-1;k++) 48 { 49 int x=k; 50 for(int j=1;j<=len;j++) 51 while(x%pr[j]==0 && x!=0) 52 {s[j]--;x/=pr[j];} 53 } 54 LL ans=1; 55 for(int i=1;i<=150;i++) 56 ans*=p_m(pr[i],s[i]); 57 printf("%lld\n",ans); 58 return 0; 59 }