A

时限: 1 Sec  内存: 256 MB

题目描述

给出n个点,每个点的度数限制为ai,现在需要选出x个点构成一棵树,要求这x个点中每个点的度数不超过这个点的度数限制,求x=1,2,……,n时能构成的不同的树的个数。 
不同的树定义为:选取的点的集合不同,或一棵树中存在边(x,y)而另一棵不存在。 
输入文件名a.in 
输出文件名a.out 

输入格式

第一行一个正整数T,代表有T组数据。 
每组数据第一行一个正整数n 
第二行n个正整数a1,a2,……,an。 

 

输出格式

对于每组数据输出一行n个数,表示x=1,x=2,……,x=n时的答案,取模1000000007。 

 

输入样例

1
3
2 2 1

输出样例

3 3 2

提示

数据规模与约定 
对于第k个测试点,n<=k*5,T<=10,0<=ai<=n 

 

 

 

 

题解

٩(๑>◡<๑)۶人生第一道prufer序列٩(๑>◡<๑)۶

prufer序列详解:https://www.cnblogs.com/chenxiaoran666/p/prufer.html

这题……其实是一个比较裸的prufer序列DP(只不过考试的时候在想矩阵树+容斥,结果B题是矩阵树+容斥。。。)

首先,我们定义一个状态:f[i][j][k]表示前i个点选j个来构成树且此时的prufer序列长度为k

然后有一个明显的转移(刷表):

f[i+1][j][k]+=f[i][j][k];(不选入树的集合)

f[i+1][j+1][k+x]+=C(k+x,x)*f[i][j][k];(选入树的集合,且为prufer序列新增x的长度)

注意此时x可以为0,因为一个可以存在于树点集合中并在prufer序列中不占长度!!!!

然后就A了。。。

 

像这种带点权限制的树的计数问题一般都与prufer序列有关

还有,就是填表的边界比较难判断,而刷表就不用考虑太多(当然必要的边界还是要有)

 

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

#define N 55
const int mod=1000000007;
int f[N][N][N],fac[N],inv[N],invf[N];
int a[N];
void shai()
{
	invf[0]=invf[1]=inv[0]=inv[1]=fac[0]=fac[1]=1;
	for(int i=2;i<=50;i++){
		fac[i]=1ll*fac[i-1]*i%mod;
		inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
		invf[i]=1ll*invf[i-1]*inv[i]%mod;
	}
}
int C(int n,int m){return 1ll*fac[n]*invf[m]%mod*invf[n-m]%mod;}

int main()
{
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	
	shai();
	int T,n,i,j,k,x,cnt;
	scanf("%d",&T);
	while(T--){
		memset(f,0,sizeof(f));
		scanf("%d",&n);cnt=0;
		for(i=1;i<=n;i++){scanf("%d",&a[i]);}
		f[0][0][0]=1;
		for(i=0;i<n;i++){
			for(j=0;j<=i;j++){
				for(k=0;k<=n-2;k++){
					if(f[i][j][k]){
						f[i+1][j][k]=(f[i+1][j][k]+f[i][j][k])%mod;
						for(x=0;x<=min(a[i+1]-1,n-k-2);x++)
							f[i+1][j+1][k+x]=(1ll*f[i+1][j+1][k+x]+1ll*f[i][j][k]*C(k+x,x))%mod;
					}
				}
			}
		}
		printf("%d",n);
		for(i=2;i<=n;i++)
			printf(" %d",f[n][i][i-2]);
		printf("\n");
	}
}