BZOJ1005 [HNOI2008]明明的烦恼

题意

Problem 1005. -- [HNOI2008]明明的烦恼

1005: [HNOI2008]明明的烦恼

Time Limit: 1 Sec   Memory Limit: 162 MB
Submit: 7014   Solved: 2769
[ Submit][ Status][ Discuss]

Description

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

Input

  第一行为N(0 < N < = 1000),
接下来N行,第i+1行给出第i个节点的度数Di,如果对度数不要求,则输入-1

Output

  一个整数,表示不同的满足要求的树的个数,无解输出0

Sample Input

3
1
-1
-1

Sample Output

2

HINT

  两棵树分别为1-2-3;1-3-2

Source

[ Submit][ Status][ Discuss]

HOME Back

分析

推荐一篇讲解prufer序列的博客

简介

Prufer数列是无根树的一种数列。在组合数学中,Prufer数列由有一个对于顶点标过号的树转化来的数列,点数为n的树转化来的Prufer数列长度为n-2。它可以通过简单的迭代方法计算出来。它由Heinz Prufer于1918年在证明cayley定理时首次提出。

具体实现

将树转换为prufer数列的方法。

总体的思路是迭代删点,直到原图中只剩下两个点。对于一棵树T,我们已经将每次找到树中标号最小的叶子结点,将这个叶子结点以及与它相邻的边删去,将与叶子结点相连

的点加入数列中。重复上一步,直到原图中只剩下两个点。

例子:以右边的树为例子,首先在所有叶子节点中编号最小的点是2,和它相邻的点的编号是3,将3加入序列并删除编号为2的点。接下来删除的点是4,5被加入序列,然后删除5,1,此时原图仅剩两个点,Prufer序列构建完成,为{3,5,1,3}

将prufer数列转换为树的方法。

例子:将结点列一个集合A={1,2,3......,n};在集合A中找出一个没有在prufer数列中出现的最小的值,将这个值在集合A中删去,并且将这个值和prufer数列中的第一个数连起一条边,并划去prufer数列中的第一个值,重复此步,直到集合A中只剩下两个数字,将以这两个数字为编号的结点连起一条边。

仍为上面的树,Prufer序列为{3,5,1,3},开始时G={1,2,3,4,5,6},未出现的编号最小的点是2,将2和3连边,并删去Prufer序列首项和G中的2。接下来连的边为{4,5},{1,5},{1,3},此时集合G中仅剩3和6,在3和6之间连边,原树恢复。

总结

可见无根树和prufer数列是唯一对应的。一棵n个节点的无根树唯一地对应了一个长度为n-2的数列,数列中的每个数都在1到n的范围内。

无根树的表示法用prufer数列。

这个数列的特点:这个点的度数-1=它在数列的出现次数。
prufer序列中某个编号出现的次数+1就等于这个编号的节点在无根树中的度数。
所以数列总长度是n-2。

Cayley公式

Cayley公式是说,一个完全图K_n有n^(n-2)棵生成树,换句话说n个节点的带标号的无根树有n^(n-2)个。

此题参照JeremyGJY的题解。

首先我们根据prufer数列可以知道任意一棵无根树可以表示为任意一个长度为\(n−2\)的串并且有以下的性质任意一点的度为\(d_i\)那么该数字将会在数列中出现\(d_i−1\)次,那么我们可以知道该数列的总长度就是\(sum=\sum_{i=1}^n{d_i-1}\)当然前提是\(n\)个度数全部已知,那么我们已经知道了\(n\)个点的度数,我们可以构造出多少不同的prufer数列呢可以发现答案就是
\[\frac{(n-2)!}{\prod_{i=1}^n(d_i-1)!}\]
但是我们现在并不知道这么多,我们现在已知的有\(cnt\)个,那么我们未知的有\(n−cnt\)个,那么我们如果不管不知道的,但是现在有\(n−2\)个空位所以答案是
\[C_{n-2}^{sum}\frac{sum!}{\prod_{i=1}^n(d_i-1)!}\]
但是现在我们还有\((n−cnt)\)个未知那么我们的答案就是
\[C_{n-2}^{sum}\frac{sum!}{\prod_{i=1}^n(d_i-1)!}\times (n-cnt)^{n-2-sum}\]
那么我们化简可以得到
\[ \frac{(n-2)!}{sum!(n-2-sum)!}\frac{sum!}{\prod_{i=1}^n(d_i-1)!}\times (n-cnt)^{n-2-sum} \\ \frac{(n-2)!}{(n-2-sum)!\prod_{i=1}^n(d_i-1)!}\times (n-cnt)^{n-2-sum} \]
因为\(n−2\)还是比较大所以靠分解质因数来解决高精度的问题。

高精度……引起了我SCOI2019不愉快的回忆。

代码

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
    rg T data=0,w=1;rg char ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
    while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
    return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;

co int N=1e3+1;
int p[N],num[N],n,d[N],cnt;
void init(){
    for(int i=2;i<=n;++i){
        if(!p[i]) p[++p[0]]=i;
        for(int j=1;j<=p[0]&&p[j]*i<=n;++j){
            p[p[j]*i]=1;
            if(i%p[j]==0) break;
        }
    }
}
void decomp(int n,int f){
    for(int i=1;i<=p[0]&&p[i]<=n;++i)
        for(int j=n;j>=p[i];j/=p[i]) num[i]+=j/p[i]*f;
}

co int mod=1e6;
int ans[N]={1},len=1;
void mul(int n){
    for(int i=0;i<len;++i) ans[i]*=n;
    for(int i=0;i<len;++i)if(ans[i]>=mod)
        ans[i+1]+=ans[i]/mod,ans[i]%=mod;
    if(ans[len]) ++len;
}
int main(){
    if((read(n))==1) return puts(read<int>()<=0?"1":"0"),0;
    init();
    for(int i=1;i<=n;++i){
        if(read(d[i])==-1) continue;
        ++cnt;
        if(d[i]<=0&&d[i]>=n) return puts("0"),0;
        if(d[i]!=-1) d[0]+=(d[i]-=1);
    }
    if(d[0]>n-2) return puts("0"),0;
    decomp(n-2,1),decomp(n-2-d[0],-1);
    for(int i=1;i<=n;++i)
        if(d[i]!=-1) decomp(d[i],-1);
    for(int i=1;i<=p[0];++i)
        while(num[i]--) mul(p[i]);
    for(int i=n-2-d[0];i;--i) mul(n-cnt);
    printf("%d",ans[len-1]);
    for(int i=len-2;i>=0;--i) printf("%06d",ans[i]);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/autoint/p/10740712.html