树的计数

结论: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

那么这棵树的个数为\frac{(n-2)!}{\prod (d-1)!},我们把他转化成prufer序列的话就是每个点出现的次数已经确定,那么该结论就显然了

[HNOI2004]树的计数

直接套用上面公式,但是细节较多,要注意特判

#include
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;
}

 

你可能感兴趣的:(计数)