BZOJ 1005: [HNOI2008]明明的烦恼

传送门

prufer序列

因为每个prufer序列唯一对应一颗树,所以如果能求出prufer序列的方案数就能求出树的方案数

一颗 n 个节点的树的prufer序列有 n-2 个数字,每个数字在 [1,n] 的范围内,表示节点编号

对于一个度数为 $d_i$ 的点,它会在prufer序列中出现 $d_i-1$ 次

设 m 为度数确定的节点数量,$d_i$ 为某个节点的度数

设 $sum=\sum _{i=1}^{m}(d_i-1)$

那么已经确定的节点在 prufer 序列中占据了 sum 个位置

这 sum 的位置在 n-2 个位置中放置的方案数显然为 $C^{sum}_{n-2}$

然后单独考虑这个长度为 sum 的序列的情况

对于确定度数的节点 1,它要在长度为 sum 的序列中选 $d_1-1$ 个位置

有 $C^{d_1-1}_{sum}$ 种方案

对于第 2 个节点,因为节点 1 先选好了,所以它要在长度为 $sum-(d_1-1)$ 的序列中选 $d_2-1$ 个位置

有 $C^{d_2-1}_{sum-(d_1-1)}$ 种方案

这样推广下去,用乘法原理把所有的 C 乘起来(化简过程自己推一下)

最后得到 $C^{sum}_{n-2}\frac{sum!}{\prod ^{m}_{i=1}(d_i-1)}$

然后考虑不确定的点,剩下 n-2-sum 个位置顺便填剩下 n-m 个数

有 $(n-m)^{(n-2-sum)}$ 种方案

最后一起乘起来就好了

答案要高精度,但是因为最后答案一定是整数,所以可以提出分子分母的质因数化简分式,不用高精除

高精怕麻烦没写压位

别忘了判断无解

 

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
typedef long long ll;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
    return x*f;
}
const int N=20007;
int n,d[N],m,sum;
int pri[N],tot,f[N];//f[i]表示i最小的质因数
bool not_pri[N];
void pre()//欧拉筛求每个数最小的质因数
{
    not_pri[1]=1; f[1]=1;
    for(int i=2;i<=n;i++)
    {
        if(!not_pri[i]) { pri[++tot]=i; f[i]=i; }//质数的最小质因数就是本身
        for(int j=1;j<=tot;j++)
        {
            int g=i*pri[j]; if(g>n) break;
            not_pri[g]=1; f[g]=pri[j];
            if(i%pri[j]==0) break;
        }
    }
}
int cnt[N];//cnt存每个质因数出现的次数
inline void work(int x,bool p)//p表示此时化简的是分子还是分母
{
    if(p) while(x!=1) cnt[f[x]]++,x/=f[x];
    else while(x!=1) cnt[f[x]]--,x/=f[x];
}
struct BIGINT//高精度存答案
{
    int a[N],len;
    BIGINT() { memset(a,0,sizeof(a)); len=1; }
    inline void mul(int tmp)//高精乘低精
    {
        for(int i=1;i<=len;i++) a[i]*=tmp;
        for(int i=1;i<=len;i++)
        {
            a[i+1]+=a[i]/10;
            a[i]%=10;
        }
        while(a[len+1]) len++,a[len+1]+=a[len]/10,a[len]%=10;
    }
    inline void print() { for(int i=len;i;i--) printf("%d",a[i]); }//输出
};
int main()
{
    int a;
    n=read(); pre();
    for(int i=1;i<=n;i++)
    {
        a=read(); if(a==-1) continue;
        if(!a) { printf("0"); return 0; }//判断无解
        d[++m]=a; sum+=a-1;
    }
    if(sum>2*n-2) { printf("0"); return 0; }//判断无解
    for(int i=n-1-sum;i<=n-2;i++) work(i,1);
    for(int i=1;i<=m;i++)
        for(int j=2;j<=d[i]-1;j++) work(j,0);
    for(int i=1;i<=n-2-sum;i++) work(n-m,1);
    BIGINT ans; ans.a[1]=1;
    for(int i=2;i<=n;i++)
        for(int j=1;j<=cnt[i];j++)
            ans.mul(i);
    ans.print();
    return 0;
}

 

posted @ 2018-12-11 19:40  LLTYYC  阅读(159)  评论(0编辑  收藏  举报