【GDSOI2017模拟】树

Description

有n个点,它们从1到n进行标号,第i个点的限制为度数不能超过A[i].
现在对于每个s (1 <= s <= n),问从这n个点中选出一些点组成大小为s的有标号无根树的方案数。

Solution

一个名叫prufer数列的东西,这是由标过号的无根树转化而来的数列,大致原理是:移去所有叶子节点(度为1的顶点)中标号最小的顶点和相连的边,并把与它相邻的点的编号加入prufer序列中,重复以上步骤直到原图仅剩2个顶点。对于这道题来说,没有直接标号,但是每个点都有度数的限制,一个点在prufer数列中允许出现的最大次数是它的度数限制-1,知道这些东西我们就可以DP了。我们设 fi,j,k 表示当前做到了第i个点,已经选择了j个点,当前数列长度为k的方案数,明显可以由 fi,j,k 推到 fi+1,j,k fi+1,j+1,k+c 。因为当我们选择了s个点,每个点出现的次数为 ci 则贡献为 s!c1+c2+...+cs ,为了方便处理,我们可以把s!拉出来最后再乘上。

Code

#include
#include
#include
#include
#include
using namespace std;
#define fo(i,a,b) for(i=a;i<=b;i++)
#define sqr(x) pow(x,2)
typedef long long ll;
const int N=102;const ll MO=1004535809;
int a[N];
int n,i,j,k,c;
ll f[N][N][N],C[N],ni[N],tot;
ll ksm(ll x,ll y){
    tot=1; 
    for(;y;y/=2,x=x*x%MO)if(y&1) y--,tot=tot*x%MO;
    return tot;
}
int main(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);

    ni[0]=C[0]=1;fo(i,1,N-1) C[i]=C[i-1]*i%MO;
    ni[N-1]=ksm(C[N-1],MO-2);
    for(i=N-2;i>0;i--) ni[i]=ni[i+1]*(i+1)%MO;
    scanf("%d",&n);
    fo(i,1,n) scanf("%d",&a[i]);
    f[0][0][0]=1;
    fo(i,0,n-1){
        fo(j,0,i){
            fo(k,0,n-2){
                f[i+1][j][k]+=f[i][j][k];
                f[i+1][j][k]=(f[i+1][j][k]>MO)?f[i+1][j][k]-MO:f[i+1][j][k];
                fo(c,0,a[i+1]-1){
                    if(k+c>n) break;
                    f[i+1][j+1][k+c]+=f[i][j][k]*ni[c]%MO;
                    f[i+1][j+1][k+c]=(f[i+1][j+1][k+c]>MO)?f[i+1][j+1][k+c]-MO:f[i+1][j+1][k+c];
                }
            }
        }
    }
    printf("%d ",n);
    fo(i,2,n){
        tot=f[n][i][i-2]*C[i-2]%MO;
        printf("%lld ",tot);
    }
}

你可能感兴趣的:(DP,GDSOI,prufer数列)