bzoj1005: [HNOI2008]明明的烦恼

传送门

膜拜PoPoQQQ大爷,题解

树的Prufer编码,以下内容摘自度娘

Prufer数列是无根树的一种数列。在组合数学中,Prufer数列由有一个对于顶点标过号的树转化来的数列,点数为n的树转化来的Prufer数列长度为n-2。它可以通过简单的迭代方法计算出来,一个树对应一个Prufer数列。

Prufer序列显然满足一个性质:一个点若度数为d,则一定在Prufer序列中出现了d-1次

于是这就变成了一个排列组合的问题了

令每个已知度数的节点的度数为di,有n个节点,m个节点未知度数,left=(n-2)-(d1-1)-(d2-1)-...-(dk-1)

已知度数的节点可能的组合方式如下

(n-2)!/(d1-1)!/(d2-1)!/.../(dk-1)!/left!

剩余left个位置由未知度数的节点随意填补,方案数为m^left

于是最后有 ans=(n-2)!/(d1-1)!/(d2-1)!/.../(dk-1)!/left!* m^left

答案很显然要高精度,为了避免高精度除法我们可以对每个阶乘暴力分解质因数,对指数进行加减操作即可。

/**************************************************************
    Problem: 1005
    User: zhouyuyang
    Language: C++
    Result: Accepted
    Time:40 ms
    Memory:1348 kb
****************************************************************/
 
#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<queue>
#define ll long long
ll mo=1000000000;
using namespace std;
struct bignum{
    ll a[405];
    bignum operator * (const bignum b) const{
        bignum c;
        memset(c.a,0,sizeof(c.a));
        for (int i=1;i<=a[0];i++){
            ll x=0;
            for (int j=1;j<=b.a[0];j++){
                x+=a[i]*b.a[j]+c.a[i+j-1];
                c.a[i+j-1]=x%mo;
                x/=mo;
            }
            c.a[i+b.a[0]]=x;
        }
        c.a[0]=a[0]+b.a[0];
        while (!c.a[c.a[0]]&&c.a[0]) c.a[0]--;
        return c;
    }
}b,c,ans;
int a[1005],d[1005],n,m,s;
inline void chai(int x,int y){
    for (int i=2;i*i<=x;i++)
        while (x%i==0){
            x/=i;
            a[i]+=y;
        }
    if (x!=1) a[x]+=y;
}//质因数分解 
void qp(int t){
    if (t==1) return;
    qp(t/2);
    c=c*c;
    if (t%2) c=c*b;
}//快速次幂 
void print(bignum b){
    printf("%lld",b.a[b.a[0]]);
    for (int i=b.a[0]-1;i;i--) printf("%09lld",b.a[i]);
}
int main(){
    scanf("%d",&n);
    for (int i=1;i<=n;i++){
        scanf("%d",&d[i]);
        if (d[i]!=-1) s+=d[i]-1; else m++;
    }
    for (int i=2;i<=n-2;i++) chai(i,1);
    chai(m,n-2-s);
    for (int i=2;i<=n-2-s;i++) chai(i,-1);
    for (int i=1;i<=n;i++)
        if (d[i]!=-1)
            for (int j=2;j<d[i];j++) chai(j,-1);
    ans.a[0]=ans.a[1]=1;
    for (int i=1;i<=n;i++)
        if (a[i]>0){
            c.a[0]=b.a[0]=1;
            c.a[1]=b.a[1]=i;
            qp(a[i]);
            ans=ans*c;
        }
    print(ans);
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值