结论:n个点的带标号的本质不同的树的个数为n^(n-2)
证明:
首先我们引入一个东西叫做prufer序列:对于一棵树,我们每次选出他的编号最小的叶子节点,在序列中加入与其相连的节点的编号,并删除这个节点,最后会剩余2个节点,这样构成的序列为这棵树所对应的prufer序列
证明1:每一棵树对应一个prufer序列
这个结论显然
证明2:每一个prufer序列唯一对应一棵树
我们思考一下我们每一次选出未在当前prufer序列中出现的未删除的编号最小的点x与当前prufer序列中的第一个点y连边,那么这条边由于prufer序列的构造过程显然在原树中,那么我们把点x删除,因为目前他的度数已经为0,把prufer序列的y给删掉,那么我们就把原问题给化简到更小的子问题,证毕
那么树与prufer序列就是一个双射
那么我们就转化为长度为n-2的序列的个数,即n^(n-2),该结论证毕
性质1:每个点在prufer序列中出现的次数为该节点的度数-1,证明平凡
扩展性质2:如果我们给每个点加上一个度数限制的话,不妨设其为d
那么这棵树的个数为,我们把他转化成prufer序列的话就是每个点出现的次数已经确定,那么该结论就显然了
[HNOI2004]树的计数
直接套用上面公式,但是细节较多,要注意特判
#include<bits/stdc++.h>
using namespace std;
int n,x;
int sum[200];
int main()
{
scanf("%d",&n);
int p=0;
for (int i=1;i<=n;i++)
{
scanf("%d",&x);
x--;
if (x<0&&n!=1) {cout << 0 << endl; return 0;}
p+=x;
if (x<2) continue;
for (int k=2;k<=x;k++){
int s=k;
for (int j=2;j*j<=s;j++)
while (s%j==0) {
sum[j]--; s/=j;
}
if (s>1) sum[s]--;
}
}
if (p!=n-2) {
cout << 0 << endl; return 0;
}
for (int k=2;k<=n-2;k++)
{
int s=k;
for (int j=2;j*j<=s;j++)
while (s%j==0) {
sum[j]++; s/=j;
}
if (s>1) sum[s]++;
}
long long ans=1;
for (int i=2;i<=150;i++)
{
if (sum[i]<0) {
cout << 0 << endl; return 0;
}
for (int j=1;j<=sum[i];j++) ans=ans*(long long)i;
}
cout << ans << endl;
}