[bzoj1211][prufer序列]树的计数

63 篇文章 0 订阅
1 篇文章 0 订阅

Description

一个有n个结点的树,设它的结点分别为v1, v2, …,
vn,已知第i个结点vi的度数为di,问满足这样的条件的不同的树有多少棵。给定n,d1, d2, …,
dn,编程需要输出满足d(vi)=di的树的个数。

Input

第一行是一个正整数n,表示树有n个结点。第二行有n个数,第i个数表示di,即树的第i个结点的度数。其中1<=n<=150,输入数据保证满足条件的树不超过10^17个。

Output

输出满足条件的树有多少棵。

Sample Input

4
2 1 2 1

Sample Output

2

题解

新东西,prufer序列
1:一个prufer序列唯一对应一个无根树
2:n个节点的无根树唯一对应了一个n-2的prufer序列
3:一个节点的度数-1等于他在这个prufer序列中出现的次数,反之亦然
4:n个节点的无根树共有n^(n-2)个
这道题只是用了第三个性质,第四个其实没有用到。。
可以想到,给出的度数d[i]-1的sigma就是这个prufer序列的长度。那么不考虑重复的情况下,这个prufer序列的全排列就是(n-2)!
考虑重复,一个节点在这个prufer序列中出现的是d[i]-1次,那么应该对于每个节点,都除以d[i]-1,d[i]=1的情况不需要处理
记得特判n=1,度数为0这些情况。
虽然说结果不会爆long long,但是中间会爆,所以质因数分解吧

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long LL;
LL pri[150];int t;
LL n,d[210],sum;
LL s[150],pr[150];
bool pd(int x)
{
    if(x==1)return false;
    int t=int(double(sqrt(x+1)));
    for(int i=2;i<=t;i++)if(x%i==0)return false;
    return true;
}
void get_pri(LL a)
{
    int p=1;
    memset(pr,0,sizeof(pr));
    while(a>1)
    {
        while(a%pri[p]==0){a/=pri[p];pr[p]++;}
        p++;if(p>t)break;
    }
}
int main()
{
    scanf("%lld",&n);sum=0;
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&d[i]);sum+=d[i]-1;
        if(n>1 && d[i]==0){printf("0\n");return 0;}
    }
    if(n==1 && d[1]!=0){printf("0\n");return 0;}
    if(sum!=n-2){printf("0\n");return 0;}
    memset(s,0,sizeof(s));t=0;
    for(int i=1;i<=150;i++)if(pd(i))pri[++t]=i;
    for(int i=1;i<=n-2;i++)
    {
        get_pri(i);
        for(int i=1;i<=t;i++)s[i]+=pr[i];
    }
    for(int i=1;i<=n;i++)
    {
        if(d[i]==1)continue;
        get_pri(d[i]-1);
        for(int i=1;i<=t;i++)s[i]-=pr[i];
    }
    LL ans=1;
    for(int i=1;i<=t;i++)
    {
        for(int j=1;j<=s[i];j++)ans*=pri[j];
    }
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值