题目
题目描述
有
n
n
n个点,它们从
1
1
1到
n
n
n进行标号,第
i
i
i个点的限制为度数不能超过
A
i
A_i
Ai
现在对于每个
s
(
1
⩽
s
⩽
n
)
s(1\leqslant s\leqslant n)
s(1⩽s⩽n),问从这
n
n
n个点中选出一些点组成大小为
s
s
s的有标号无根树的方案数
m
o
d
1004535809
mod~1004535809
mod 1004535809
输入格式
第一行一个整数
n
n
n
第二行
n
n
n个整数表示
A
i
A_i
Ai
输出格式
输出一行 n n n个整数,第 i i i个整数表示 s = i s=i s=i时的答案。
样例
样例输入
3
2
2 1
样例输出
3 3 2
数据范围与提示
20
%
20\%
20%的数据:
n
<
6
n<6
n<6
60
%
60\%
60%的数据:
n
⩽
50
n\leqslant 50
n⩽50
100
%
100\%
100%的数据:
n
⩽
100
n\leqslant 100
n⩽100
题解
要完成这一题,需要知道一个神奇的数列——prufer数列!
每一棵不同的无根树,都对应这不同的prufer数列
也就是说,prufer数列和无根树是一一对应的
所以我们只需要计算可以组成多少种prufer数列
prufer数列有一个神奇的性质——每一个节点出现的次数是它的度数减一
这就是我们为什么要使用prufer数列了
我们假设计算前
i
i
i个点并且每个点的度数是
D
i
D_i
Di,那么总共就有
(
i
−
2
)
!
∑
i
=
1
n
D
i
!
\frac{(i-2)!}{\sum \limits_{i=1}^{n}D_i!}
i=1∑nDi!(i−2)!
这是一个简单的有限可重全排列
我们用
f
i
,
j
,
k
f_{i,j,k}
fi,j,k表示计算前
i
i
i个点,已经选出了
j
j
j个点,序列长度为
k
k
k的方案数除以
(
i
−
2
)
!
(i-2)!
(i−2)!
那么,我们就可以进行转移:
f
i
+
1
,
j
,
k
+
=
f
i
,
j
,
k
,
f
i
+
1
,
j
+
1
,
k
+
d
+
=
f
i
,
j
,
k
×
1
d
!
f_{i+1,j,k}+=f_{i,j,k},f_{i+1,j+1,k+d}+=f_{i,j,k}\times \frac{1}{d!}
fi+1,j,k+=fi,j,k,fi+1,j+1,k+d+=fi,j,k×d!1
那么,答案就是
f
n
,
i
,
i
−
2
×
(
i
−
2
)
!
f_{n,i,i-2}\times (i-2)!
fn,i,i−2×(i−2)!
附上代码:
#include<algorithm>
#include<cstdio>
using namespace std;
int n,a[110];
long long MOD=1004535809,jc[110],ny[110],f[110][110][110];
long long POW(long long a,int b)
{
long long ans=1;
for(;b;b>>=1){
if(b&1) ans=ans*a%MOD;
a=a*a%MOD;
}
return ans;
}
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
scanf("%d",&n),jc[0]=ny[0]=f[0][0][0]=1;
for(int i=1;i<=n;i++) scanf("%d",&a[i]),jc[i]=jc[i-1]*i%MOD;
ny[n]=POW(jc[n],MOD-2);
for(int i=n-1;i;i--) ny[i]=ny[i+1]*(i+1)%MOD;
for(int i=0;i<n;i++) for(int j=0;j<=i;j++) for(int k=0;k<=n;k++) if(f[i][j][k]){
f[i+1][j][k]=(f[i+1][j][k]+f[i][j][k])%MOD;
int len=min(n-k-2,a[i+1]-1);
for(int l=0;l<=len;l++) f[i+1][j+1][k+l]=(f[i+1][j+1][k+l]+f[i][j][k]*ny[l]%MOD)%MOD;
}
printf("%d",n);
for(int i=2;i<=n;i++) printf(" %lld",f[n][i][i-2]*jc[i-2]%MOD);
}