JZOJ 5068.树

来源:互联网 发布:c语言笔试题和答案 编辑:程序博客网 时间:2024/06/09 23:26

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|!ΠiACi

所以我们列DP。
刚刚开始我想到的显然是f[i][j]表示做到i选了j个,但是序列长度维护不了。
所以设f[i][j][k]表示做到i选了j个,选了这j个数,Prufer序列长度为k的方案数。
DP方程:
f[i][j][k]+=f[i1][j][k]

f[i][j][k]+=f[i1][j1][kl]1l!

选i个答案为f[n][i][i2](i2)!

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;}
原创粉丝点击