Prufer编码:51nod 1806 wangyurzee的树

版权声明:原创文章,转载要注明作者哦 https://blog.csdn.net/DYT_B/article/details/83507160

题目描述:戳这里
题解:
这题用到了一个叫做prufer编码的东西,感觉还是比较有用的。

首先题目给了若干限制条件,某一个点的度数不能为某一个(或者一些)值。这个东西显然比较难求,那么我们可以容斥一下。我们把条件变成有多少点的度数为某一个值,那么可以求出有至少多少条件被满足了,答案就是恰好有0个条件被满足的方案数(观察到条件总数比较小,可以暴枚)。

那么题目就变成求限定某些点的度数的生成树(无根树)的数量。
简化一下问题,我们先求n个点的生成树的数量

这里就要引入prufer编码的概念了。
prufer编码是一种生成方式,可以把一颗无根树(点不相同)变成一个长度为n-2的序列,也可以把一个prufer序列变成一颗无根树。
那么接下来就讲讲具体生成方法(这里只讨论点数大于1的树)。
1.树->序列
我们每次找到一个编号最小的叶子节点,把它从树中取出,并且把它的父节点的编号放入序列中,直到树中只剩下两个点,此时停止。
2.序列->树
设集合A -> 1,2,3,…,n-1,n
purfer序列 a1,a2,… ,an-2
顺次选出purfer数列首位元素,然后在集合A中选出另一元素与它相连边并且去掉
选出元素需满足三个条件
1.不能在prufer序列中
2.应在集合A中
3.序号最小
不断进行以上操作,直到prufer数列为空。此时A集合必然存在两个元素,将这两个元素连接起来。
那么由于以上方法使得一颗无根树和一个序列一一对应,所以可以证明这样的生成方式是唯一的。

知道了这个条件对我们有什么帮助呢。
我们可以先解决简单的问题:n个点的生成树的数量
这个东西直接就可以写出来了: n n 2 n^{n-2} (prufer序列中的每一个元素都从1~n)

那么如果一些点的度数有限制呢?

那么考虑度数和边数有关,一个点的度数为x,那么它肯定在prufer序列中出现了x-1次。
这个性质显然,因为出现了x-1次以后,这个点就变成了叶节点。

那么如果一些点的度数确定了,生成树的方案数就可以求了。

假设总共有n个点,有x个点是没有限制的,y个点有限制,度数分别为{du1,du2,…,duy}, d u i 1 = D \sum dui-1=D
那么x个点可以随便放,那么就是 x n 2 D x^{n-2-D}
剩下的点分别在purfer序列中出现了{du1-1,du2-2,…,duy-y}次,就是重排列的方案数:
( n 2 D ) ! ( d u 1 1 ) ! ( d u 2 2 ) ! . . . ( d u y y ) ! \frac{(n-2-D)!}{(du1-1)!(du2-2)!...(duy-y)!}
所以总共的方案数就是 ( n 2 D ) ! ( d u 1 1 ) ! ( d u 2 2 ) ! . . . ( d u y y ) ! x n 2 D \frac{(n-2-D)!}{(du1-1)!(du2-2)!...(duy-y)!}x^{n-2-D}

公式套一套,就可一做出来了。

还有几个小坑:
1.树的大小如果为1,直接输出1(prufer序列不能处理!!!)
2.一个点可能有多个限制条件,如果同时满足了多个条件,那么直接continue

代码如下:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1000005,maxm=20,tt=1e9+7;
int n,m,u[maxm],d[maxm],pw[maxn],vis[maxn];
ll ans;
ll qsm(ll x,int y){
	ll ret=1;
	while (y){
		if (y%2==1) ret=1ll*ret*x%tt;
		x=x*x%tt; y/=2;
	}
	return ret;
}
int main(){
	scanf("%d%d",&n,&m);
	if (n==1) {printf("1\n"); return 0;}
	for (int i=1;i<=m;i++) scanf("%d%d",&u[i],&d[i]);
	pw[0]=1;
	for (int i=1;i<=n;i++) pw[i]=1ll*pw[i-1]*i%tt;
	for (int j=0;j<(1<<m);j++){
		int s=0,sum=0;
		bool check=0;
		for (int i=1;i<=m;i++)
			if (j&(1<<i-1)) {
				s++,sum+=d[i]-1;
				if (vis[u[i]]==j) {check=1; break;}
				else vis[u[i]]=j;
			}
		if (check==1||sum>n-2) continue;
		ll now=qsm(1ll*(n-s),n-2-sum)*pw[n-2]%tt*qsm(1ll*pw[n-2-sum],tt-2)%tt;
		for (int i=1;i<=m;i++)
		if (j&(1<<i-1)) now=now*qsm(pw[d[i]-1],tt-2)%tt;
		if (s%2==0) ans+=now; else ans-=now;
		ans=(ans%tt+tt)%tt;
	}
	printf("%lld\n",ans);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/DYT_B/article/details/83507160