HNOI2004 树的计数 | HNOI2008 明明的烦恼

题目链接:戳我

prufer序列的问题。

prufer序列和无根树是一一对应的。而且在树中度数为k的点,在prufer序列中的出现次数为\(k-1\)次。

根据有限制次数的可重复元素的排列计数公式,我们可以知道答案是\(\frac{(n-2)!}{(du[1]-1)\times (du[2]-1)\times ... \times (du[n]-1)}\)

因为乘法中间可能会爆long long,所以采用分解质因数的方式。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdio>
#define MAXN 100010
using namespace std;
int n,m,tot,cnt;
int d[MAXN],num[MAXN],prime[MAXN];
long long ans=1;
long long s[MAXN];
inline bool check(int x)
{
    for(int i=2;i<=sqrt(x);i++)
        if(x%i==0) return false;
    return true;
}
inline void get_prime()
{
    for(int i=2;i<=150;i++)
        if(check(i))
            prime[++cnt]=i;
}
inline void solve(long long x,int f)
{
    for(int i=1;i<=cnt;i++)
    {
        if(x<=1) return;
        while(x%prime[i]==0)
            num[i]+=f,x/=prime[i];
    }
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("ce.in","r",stdin);
    #endif
    s[1]=1;
    for(int i=2;i<=22;i++) s[i]=s[i-1]*i;
    get_prime();
    scanf("%d",&n);
    if(n==1)
    {
        int x;
        scanf("%d",&x);
        if(!x) printf("1\n");
        else printf("0\n");
        return 0;
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&d[i]);
        if(!d[i]){printf("0\n");return 0;}
        d[i]--;
        tot+=d[i];
    }
    if(tot!=n-2){printf("0\n");return 0;}
    solve(s[n-2],1);
    for(int i=1;i<=n;i++) solve(s[d[i]],-1);
    for(int i=1;i<=cnt;i++)
        while(num[i]--)
            ans*=prime[i];
    printf("%lld\n",ans);
    return 0;
}

这个题是明明的烦恼的弱化版。
不过如果会做这个题,应该也会做那个题了。

现在我们只知道cnt个点的最终度数,我们假设\(sum=\sum_{i=1}^n (du[i]-1)\)那么现在的prufer序列的种类数应该是\(C_{n-2}^{sum}\times \frac{sum!}{\prod_{i=1}^{cnt} (du[i]-1)!}\)

而剩下来还有\(n-2-sum\)个位置,每个位置都可以填入除了cnt这些点的其他所有点,所以刚才的式子乘上一个\((n-cnt)^{n-2-sum}\)就行了。

posted @ 2019-07-10 15:04  风浔凌  阅读(175)  评论(0编辑  收藏  举报