Description
给出n个正整数ai,现从n个点中选s个点构成一棵树,使得编号为i的节点度数不超过ai,问所有可能情况数
Input
第一行一整数T表示用例组数,每组用例首先输入一整数n,之后n个整数ai
(1<=T<=10,2<=n<=50,1<=ai < n)
Output
对于每组用例,输出n个数,第s个数表示节点数为s的合法的树的数量,结果模1e9+7(1<=s<=n)
Sample Input
1
3
2 2 1
Sample Output
3 3 2
Solution
知道点的度数求可以构造的树的数量一般用到Purfer序列,因为一个长度为n-2的Purfer序列唯一对应一个n个点的树,且Purfer序列中i出现的次数就是节点i的度数减一,此题只给出点度数的上限,所以可以考虑dp
用dp[i][j][j]表示从前i个点中选取j个点且这j个点在Purfer序列中出现k次的合法Purfer序列数(也就是合法树的数量),考虑第i个点是否被选,以及被选后在Purfer序列出现的次数有以下转移方程:
dp[i][j][k]+=dp[i-1][j][k](不选i)
dp[i][j+1][k+d]+=C[k+d][d]*dp[i-1][j][k],d=0,1,…,a[i]-1(选i,出现d次,组合数C(k+d,d)表示在一个已经有序的长度为k的序列中插入d个相同的数的方案数)
dp[n][i][n-2]即为答案(1<=i<=n)
Code
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
#define mod 1000000007ll
#define maxn 55
int T,n,a[maxn];
ll C[maxn][maxn],dp[maxn][maxn][maxn];
void init()
{
memset(C,0,sizeof(C));
C[0][0]=1;
for(int i=1;i<=50;i++)
{
C[i][0]=C[i][i]=1;
for(int j=1;j<i;j++)C[i][j]=(C[i][j-1]+C[i-1][j-1])%mod;
}
}
int main()
{
init();
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
memset(dp,0,sizeof(dp));
dp[0][0][0]=1;
for(int i=1;i<=n;i++)
for(int j=0;j<i;j++)
for(int k=0;k<=n-2;k++)
{
dp[i][j][k]=(dp[i][j][k]+dp[i-1][j][k])%mod;
for(int d=0;d<a[i]&&k+d<=n-2;d++)
dp[i][j+1][k+d]=(dp[i][j+1][k+d]+C[k+d][d]*dp[i-1][j][k]%mod)%mod;
}
printf("%d",n);
for(int i=2;i<=n;i++)printf(" %I64d",dp[n][i][i-2]);
printf("\n");
}
return 0;
}