Problem
Description
有n个点,它们从1到n进行标号,第i个点的限制为度数不能超过A[i].
现在对于每个s (1 <= s <= n),问从这n个点中选出一些点组成大小为s的有标号无根树的方案数。
Input
第一行一个整数n.
第二行n个整数表示A[i].
Output
输出一行n个整数,第i个整数表示s=i时的答案。答案模1004535809.
Sample Input
3
2 2 1
Sample Output
3 3 2
Solution
首先看到无根树,想到Prufer序列。
一棵无根树对应着一个Prufer序列,且有一个性质特别地重要:假设有一点x度数为d[x],那么在Prufer序列中会出现d[x]-1次。
证明:
首先点i度数为d[i],说明有d[i]个点连着他,将与之连着的d[i-1]个点删掉的时候,假设没被删的与之连着的点为j,那么会有几种情况:
①当前没被删的节点只剩2个。那么得证。
②当前没被删的节点超过2个:
⑴删去与j连着的点及“子树”,此时最后剩下i和j两个点,转①。
⑵此时i点是叶子节点,那么i点可能是所有叶子节点中标号最小的,得证。否则就转②。
我们可以很容易地得知,选出的点集为A,这些点在Prufer序列中出现了Ci次,那么这些点组成一棵树的方案为:
|A|!Πi∈ACi!
所以我们列DP。
刚刚开始我想到的显然是 f[i][j] 表示做到i选了j个,但是序列长度维护不了。
所以设 f[i][j][k] 表示做到i选了j个,选了这j个数,Prufer序列长度为k的方案数。
DP方程:
f[i][j][k]+=f[i−1][j][k]
f[i][j][k]+=f[i−1][j−1][k−l]∗1l!
选i个答案为 f[n][i][i−2]∗(i−2)! 。
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 105
#define mo 1004535809
#define LL long long
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
LL f[N][N][N];
LL a[N],jc[N],ny[N];
LL i,j,k,l,n,m,tmp;
LL ksm(LL x,LL y){
LL res=1;
while (y){
if (y%2) res=(res*x)%mo;
x=(x*x)%mo;
y/=2;
}
return res;
}
int main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
scanf("%lld",&n);
fo(i,1,n) scanf("%lld",&a[i]);
jc[0]=jc[1]=ny[0]=ny[1]=1;
fo(i,2,n) jc[i]=(jc[i-1]*i)%mo;
ny[n]=ksm(jc[n],mo-2);
fd(i,n-1,1) ny[i]=(ny[i+1]*(i+1))%mo;
f[0][0][0]=1;
fo(i,1,n){
fo(j,0,n){
fo(k,0,n){
f[i][j][k]=(f[i][j][k]+f[i-1][j][k])%mo;
tmp=min(k,a[i]-1);
if(j)fo(l,0,tmp)f[i][j][k]=(f[i][j][k]+f[i-1][j-1][k-l]*ny[l])%mo;
}
}
}
printf("%lld",n);
fo(i,2,n)printf(" %lld",(f[n][i][i-2]*jc[i-2])%mo);
return 0;
}