【树】【数论】[BZOJ1005][HNOI2008]明明的烦恼

题目描述

自从明明学了树的结构,就对奇怪的树产生了兴趣…… 给出标号为1到N的点,以及某些点最终的度数,允许在任意两点间连线,可产生多少棵度数满足要求的树?

样例输入

3
1
-1
-1

样例输出

2

题目解析

首先我们根据prufer数列可以知道任意一棵无根树可以表示为任意一个长度为 n2 的串并且有以下的性质任意一点的度为 di 那么该数字将会在数列中出现 di1 次,那么我们可以知道该数列的总长度就是 sum=ni=1di1 当然前提是 n 个度数全部已知,那么我们已经知道了n个点的度数,我们可以构造出多少不同的prufer数列呢可以发现答案就是

(n2)!ni=1(di1)!
但是我们现在并不知道这么多,我们现在已知的有 cnt 个,那么我们未知的有 ncnt 个,那么我们如果不管不知道的,但是现在有 n2 个空位所以答案是
Csumn2sum!ni=1(di1)!
但是现在我们还有 (ncnt) 个未知那么我们的答案就是
Csumn2sum!ni=1(di1)!×(ncnt)n2sum
那么我们化简可以得到
(n2)!sum!(n2sum)!sum!ni=1(di1)!×(ncnt)n2sum
(n2)!(n2sum)!ni=1(di1)!×(ncnt)n2sum
因为 n2 还是比较大所以靠分解质因数来解决高精度的问题。



关于prufer唯一性其实很好证明如果两个prufer的数列是一样的那么意味着每一个节点的每一个儿子数量相同,并且出入度和位置相同,那难道不一样么。

代码

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 1000;
int answ[MAXN+10], prime[MAXN+10], t[MAXN*10+10], d[MAXN+10];
bool notprime[MAXN+10];
void GetPrime(int Max){
    int tmp;
    for(int i=2;i<=Max;i++){
        if(!notprime[i])
            prime[++prime[0]] = i;
        for(int j=1;j<=prime[0]&&(tmp=prime[j]*i)<=Max;j++){
            notprime[tmp] = true;
            if(i%prime[j] == 0)
                break;
        }
    }
}
void add(int u, int m){
    for(int i=1;i<=prime[0]&&u>1;i++){
        while(u%prime[i]==0){
            u /= prime[i];
            answ[i] += m;
        }
    }
}
int main(){
    int n;
    scanf("%d", &n);
    int sum=0, cnt=0, flag = 0;
    GetPrime(1000);
    int t1;
    for(int i=1;i<=n;i++){
        scanf("%d", &t1);
        d[i] = t1;
        if(t1 == 0 || t1 >= n) flag = 1;
        if(t1 == -1){
            continue;
        }else{
            cnt++;
            sum += (--t1);
            for(int j=1;j<=t1;j++)
                add(j, -1);
        }
    }
    t1 = n-2;
    for(int i=1;i<=t1;i++)
        add(i, 1);
    if(n == 1){
        if(t1 == -1) printf("1\n");
        else printf("0\n");
        return 0;
    }
    if(n == 2){
        if((d[1]==0||d[1]>1) || (d[2]==0||d[2]>1)) printf("0\n");
        else printf("1\n");
        return 0;
    }
    if(flag){
        printf("0\n");
        return 0;
    }
    t1 = n-2-sum;
    for(int i=1;i<=t1;i++)
        add(i, -1);
    add(n-cnt, n-2-sum);
    t[0] = t[1] = 1;
    for(int i=1;i<=prime[0];i++){
        while(answ[i]){
            answ[i]--;
            for(int j=1;j<=t[0];j++)
                t[j] *= prime[i];
            for(int j=1;j<t[0];j++){
                t[j+1] += t[j] / 10;
                t[j] %= 10;
            }
            while(t[t[0]] >= 10){
                t[t[0]+1] = t[t[0]]/10;
                t[t[0]] %= 10;
                t[0]++;
            }
        }
    }
    for(int i=t[0];i;i--)
        printf("%d", t[i]);
    printf("\n");

    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值