【Prufer数列/组合数学】[HNOI2008][HYSBZ/BZOJ1005]明明的烦恼

题目链接

分析

Prufer数列

生成Prufer数列

由一棵树得到它的 Prufer Sequence 总共需要 n-2 步,每一步都在当前的树中寻找具有最小标号的叶子节点(度为 1),将与其相连的点的标号设为 Prufer Sequence 的第 i 个元素,并将此叶子节点从树中删除,直到最后得到一个长度为 n-2 的 Prufer Sequence 和一个只有两个节点的树。
所以一个树,只能得到一个唯一的 Prufer Sequence。

由Prufer数列生成树

先将所有编号为 1 到 n 的点的度赋初值为 1,然后加上它在 Prufer Sequence 中出现的次数,得到每个点的度
先执行 n-2 步,每一步,选取具有最小标号的度为 1 的点 u 与 Prufer Sequence 中的第 i 个数 v 表示的顶点相连,得到树中的一条边,并将 u 和 v 的度减1
最后再把剩下的两个度为 1 的点连边,加入到树中
所以Prufer Sequence和一个树唯一对应。
得到

定理:一个 Prufer Sequence 和一棵树一一对应

如果还不不清楚,请自行百度

回到这道题

一个点在 Prufer Sequence 出现的次数等于它的度数减一,我们考虑有确定度数的点,给他们编号1~cnt,第i个点的度数为 di

sum=i=1cntdi

如果没有不确定度数的点,那么方案数为全排列。
P=sum!cnti=1(di1)

然而有不确定的点那么,这些确定的点会放在n-2个位置中的sum个,所以
C(sumn2)P

剩下的n-2-sum个位置,每个位置都可以从n-cnt个点中任意选择一个来放。
ans=C(sumn2)P(ncnt)n2sum=(n2)!sum!(n2sum)!sum!cnti=1(di1)(ncnt)n2sum=(n2)!(n2sum)!cnti=1(di1)(ncnt)n2sum

用高精度计算,除法不好做,可先分解质因数。

代码

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
#define MAXN 1000
#define MAXLEN 3000
int n,d[MAXN+10],p[MAXN+10],pcnt,fcnt[MAXN+10],cnt,sum;
bool f[MAXN+10];
void prepare(){
    int i,j;
    for(i=2;i<=n;i++){
        if(!f[i])
            p[++pcnt]=i;
        for(j=1;p[j]*i<=n;j++){
            f[i*p[j]]=1;
            if(i%p[j]==0)
                break;
        }
    }
}
struct hp{
    int a[MAXLEN+10];
    inline hp(){
        memset(a,0,sizeof a);
    }
    inline hp(hp &a){
        *this=a;
    }
    void int2hp(int b){
        while(b)
            a[++a[0]]=b%10,b/=10;
        if(!a[0])
            a[0]++;
    }
    inline hp(int b){
        memset(a,0,sizeof a);
        int2hp(b);
    }
    void operator*=(const hp &b){
        hp c;
        int len=a[0]+b.a[0],i,j;
        for(i=1;i<=a[0];i++)
            for(j=1;j<=b.a[0];j++)
                c.a[i+j-1]+=a[i]*b.a[j];
        for(i=1;i<=len;i++){
            c.a[i+1]+=c.a[i]/10;
            c.a[i]%=10;
        }
        while(!c.a[len]&&len)
            len--;
        c.a[0]=len;
        *this=c;
    }
    void print(){
        for(int i=a[0];i;i--)
            printf("%d",a[i]);
    }
}ans(1);
void Read(int &x){
    char c;
    bool f=0;
    while(c=getchar(),c!=EOF){
        if(c=='-')
            f=1;
        if(c>='0'&&c<='9'){
            x=c-'0';
            while(c=getchar(),c>='0'&&c<='9')
                x=x*10+c-'0';
            ungetc(c,stdin);
            if(f)
                x=-x;
            return;
        }
    }
}
void read(){
    Read(n);
    if(n==1){
        Read(d[1]);
        if(d[1]<=0)
            puts("1");
        else
            puts("0");
        exit(0);
    }
    for(int i=1;i<=n;i++){
        Read(d[i]);
        if(d[i]>=n||!d[i]){
            puts("0");
            exit(0);
        }
        if(d[i]>-1)
            sum+=d[i]-1,cnt++;
    }
    if(sum>n-2){
        puts("0");
        exit(0);
    }
}
void de_factor(int n,int d){
    int i,t=sqrt(n+0.5);
    for(i=1;p[i]<=t&&n>1;i++)
        while(n%p[i]==0)
            fcnt[p[i]]+=d,n/=p[i];
    if(n>1)
        fcnt[n]+=d;
}
hp quick_pow(int a,int b){
    hp c(a),ret;
    ret.a[0]=1,ret.a[1]=1;
    while(b){
        if(b&1)
            ret*=c;
        c*=c;
        b>>=1;
    }
    return ret;
}
void solve(){
    int i,j;
    for(i=n-2;i>n-2-sum;i--)
        de_factor(i,1);
    for(i=1;i<=n;i++)
        if(d[i]>1)
            for(j=d[i]-1;j;j--)
                de_factor(j,-1);
    for(i=1;i<=pcnt;i++)
        ans*=quick_pow(p[i],fcnt[p[i]]);
    ans*=quick_pow(n-cnt,n-2-sum);
}
int main()
{
    read();
    prepare();
    solve();
    ans.print();
}

你可能感兴趣的:(C++,组合数学,bzoj,prufer数列,hnoi)