[BZOJ1005][HNOI2008]明明的烦恼&&[BZOJ1211][HNOI2004]树的计数【prufer序列】

【题目描述】

 自从明明学了树的结构,就对奇怪的树产生了兴趣......给出标号为1到N的点,以及某些点最终的度数,允许在
任意两点间连线,可产生多少棵度数满足要求的树?

【题解】

    每个prufer序列对应一棵树。

    每个点在prufer序列中出现的次数=度数-1,组合数计算即可。

    记得特判 n==1 的情况。

/* --------------
    user Vanisher
    problem bzoj-1005 && bzoj-1211
----------------*/
# include <bits/stdc++.h>
# define	N 		1010 
# define 	MN 		5010
using namespace std;
int a[N],cnt,n,inc[N],num[N][N],p[N],pnum,now;
struct number{
	int num[MN];
}t,x,ans;
number operator *(number x, number y){
	memset(t.num,0,sizeof(t.num));
	for (int i=1; i<=x.num[0]; i++)
		for (int j=1; j<=y.num[0]; j++)
			t.num[i+j-1]+=x.num[i]*y.num[j];
	t.num[0]=x.num[0]+y.num[0]-1; int i;
	for (i=1; i<=t.num[0]||t.num[i]!=0; i++)
		t.num[i+1]+=t.num[i]/10, t.num[i]%=10;
	t.num[0]=i-1;
	return t;
}
number mypow(int xx, int y){ 
	memset(x.num,0,sizeof(x.num));
	while (xx>0){
		x.num[0]++;
		x.num[x.num[0]]=xx%10;
		xx/=10;
	}
	number i=x; 
	x.num[0]=1; x.num[1]=1;
	while (y>0){
		if (y%2==1) x=x*i;
		i=i*i;
		y/=2;
	}
	return x;
}
void pre(int n){
	for (int i=2; i<=n; i++){
		int j=i;
		for (int k=2; k*k<=j; k++)
			while (j%k==0){
				num[i][k]++;
				j/=k;
			}
		if (j!=1) num[i][j]++;
		if (j==i) p[++pnum]=i;
	}
}
int C(int n, int m){
	for (int i=n; i>=n-m+1; i--) inc[i]++;
	for (int i=1; i<=m; i++) inc[i]--;
}
int read(){
	int tmp=0, fh=1; char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') fh=-1; ch=getchar();}
	while (ch>='0'&&ch<='9'){tmp=tmp*10+ch-'0'; ch=getchar();}
	return tmp*fh;
}
int main(){
	n=read(); 
	pre(n); 
	ans.num[0]=1, ans.num[1]=1;
	for (int i=1; i<=n; i++){
		a[i]=read();
		if (a[i]==0&&n!=1){
			puts("0");
			return 0;
		}
		if (a[i]>0) now=now+(a[i]-1);
	}
	if (n==1&&a[1]==0) {
		puts("1");
		return 0;
	}
	if (now!=n-2){
		puts("0");
		return 0;
	}
	if (now>n-2)
		printf("%d\n",0);
		else{
			now=n-2, cnt=n;
			for (int i=1; i<=n; i++){
				if (a[i]>0){
					cnt--;
					C(now,a[i]-1);
					now=now-(a[i]-1);
				}
			}
			for (int i=1; i<=n; i++){
				int tmp=inc[i]; inc[i]=0;
				for (int j=1; j<=pnum; j++)
					inc[p[j]]+=num[i][p[j]]*tmp;
			}
			for (int i=1; i<=pnum; i++)
				ans=ans*mypow(p[i],inc[p[i]]);
			ans=ans*mypow(cnt,now);
			for (int i=ans.num[0]; i>=1; i--)
				printf("%d",ans.num[i]);
			printf("\n"); 
		}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值