Description
自从明明学了树的结构,就对奇怪的树产生了兴趣……给出标号为1到N的点,以及某些点最终的度数,允许在
任意两点间连线,可产生多少棵度数满足要求的树?
Input
第一行为N(0 < N < = 1000),
接下来N行,第i+1行给出第i个节点的度数Di,如果对度数不要求,则输入-1
Output
一个整数,表示不同的满足要求的树的个数,无解输出0
Sample Input
3
1
-1
-1
Sample Output
2
HINT
两棵树分别为1-2-3;1-3-2
分析:
1.树的Prufer编码的实现及其性质:
①每次都取出度数为1的节点,并输出与该节点相邻的点。重复此操作,直到这棵树只剩下两个点。
②任意一棵树都存在一个长度为n-2的Prufer序,度数为m的节点在Prufer序中出现m-1次
③n个节点度数分别为D1,D2,D3…Dn的树的个数为
(n−2)!(D1−1)!(D2−1)!...(Dn−1)!
(其实就是一个有重复元素的全排列)
2.本题需要用到Prufer序编码的相关知识
3.如果没有-1的点,那么答案就是性质③
4.把所有度数没有限制的点在一起考虑,设m为没有限制的节点个数,tot为限制的节点度数的和,则它们一共有
mn−2−tot
种不同的方案
5.最终的答案为
(n−2)!mn−2−tot(n−2−tot)!(D1−1)!(D2−1)!...(Dn−1)!
加上高精度就可以过了。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int maxn=10010;
int a[maxn];
int n;
int sum;
int tot[maxn];
struct Bigint{
int a[maxn],len;
Bigint(){
len=1;
memset(a,0,sizeof(a));
}
Bigint operator *(int& x)const{
Bigint ret;
ret.len=len;
for(int i=1;i<=len;i++) ret.a[i]=a[i]*x;
for(int i=1;i<=len;i++) ret.a[i+1]+=ret.a[i]/10,ret.a[i]%=10;
while(ret.a[ret.len+1]) ret.len++,ret.a[ret.len+1]=ret.a[ret.len]/10,ret.a[ret.len]%=10;
return ret;
}
}A;
int cnt;
int is_prime[maxn],prime[maxn];
void getprime(){
for(int i=2;i<=1000;i++){
if(!is_prime[i]) prime[++cnt]=i;
for(int j=1;j<=cnt && i*prime[j]<=1000;j++){
is_prime[i*prime[j]]=1;
if(i%prime[j]==0) break;
}
}
}
void calc(int x,int c){
for(int i=2;i<=x;i++){
int p=i;
for(int j=1;j<=cnt;j++){
if(p<=1) break;
while(p%prime[j]==0){
p/=prime[j];tot[j]+=c;
}
}
}
}
int m;
int main(){
scanf("%d",&n);
if(n==1){
int x;
scanf("%d",&x);
if(x)printf("0");
else printf("1");
return 0;
}
getprime();
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
if(a[i]>=n){
printf("0");
return 0;
}
if(a[i]!=-1) sum+=a[i]-1;
else m++;
}
if(sum>n-2){
printf("0");
return 0;
}
calc(n-2,1);
calc(n-2-sum,-1);
for(int i=1;i<=n;i++)if(a[i]!=-1){
calc(a[i]-1,-1);
}
A.a[1]=1;
for(int i=1;i<=cnt;i++)
for(int j=1;j<=tot[i];j++)
A=A*prime[i];
for(int i=1;i<=n-2-sum;i++) A=A*m;
for(int i=A.len;i>=1;i--) printf("%d",A.a[i]);
return 0;
}
^_^