题目描述:戳这里
题解:
这题用到了一个叫做prufer编码的东西,感觉还是比较有用的。
首先题目给了若干限制条件,某一个点的度数不能为某一个(或者一些)值。这个东西显然比较难求,那么我们可以容斥一下。我们把条件变成有多少点的度数为某一个值,那么可以求出有至少多少条件被满足了,答案就是恰好有0个条件被满足的方案数(观察到条件总数比较小,可以暴枚)。
那么题目就变成求限定某些点的度数的生成树(无根树)的数量。
简化一下问题,我们先求n个点的生成树的数量。
这里就要引入prufer编码的概念了。
prufer编码是一种生成方式,可以把一颗无根树(点不相同)变成一个长度为n-2的序列,也可以把一个prufer序列变成一颗无根树。
那么接下来就讲讲具体生成方法(这里只讨论点数大于1的树)。
1.树->序列
我们每次找到一个编号最小的叶子节点,把它从树中取出,并且把它的父节点的编号放入序列中,直到树中只剩下两个点,此时停止。
2.序列->树
设集合
A
−
>
1
,
2
,
3
,
.
.
.
,
n
−
1
,
n
A -> {1,2,3,...,n-1,n}
A−>1,2,3,...,n−1,n
purfer序列
a
1
,
a
2
,
.
.
.
,
a
n
−
2
a_1,a_2,... ,a_{n-2}
a1,a2,...,an−2
顺次选出purfer数列首位元素,然后在集合A中选出另一元素与它相连边并且去掉
选出元素需满足三个条件:
1.不能在prufer序列中
2.应在集合A中
3.序号最小
不断进行以上操作,直到prufer数列为空。此时A集合必然存在两个元素,将这两个元素连接起来。
那么由于以上方法使得一颗无根树和一个序列一一对应,所以可以证明这样的生成方式是唯一的。
知道了这个条件对我们有什么帮助呢。
我们可以先解决简单的问题:n个点的生成树的数量
这个东西直接就可以写出来了:
n
n
−
2
n^{n-2}
nn−2(prufer序列中的每一个元素都从1~n)
那么如果一些点的度数有限制呢?
那么考虑度数和边数有关,一个点的度数为x,那么它肯定在prufer序列中出现了x-1次。
这个性质显然,因为出现了x-1次以后,这个点就变成了叶节点。
那么如果一些点的度数确定了,生成树的方案数就可以求了。
假设总共有n个点,有x个点是没有限制的,y个点有限制,度数分别为
d
u
1
,
d
u
2
,
.
.
.
,
d
u
y
{du_1,du_2,...,du_y}
du1,du2,...,duy,
∑
d
u
i
−
1
=
D
\sum du_i-1=D
∑dui−1=D
那么x个点可以随便放,那么就是
x
n
−
2
−
D
x^{n-2-D}
xn−2−D
剩下的点分别在purfer序列中出现了
d
u
1
−
1
,
d
u
2
−
1
,
.
.
.
,
d
u
y
−
1
{du_1-1,du_2-1,...,du_y-1}
du1−1,du2−1,...,duy−1次,就是重排列的方案数:
(
n
−
2
−
D
)
!
(
d
u
1
−
1
)
!
(
d
u
2
−
1
)
!
.
.
.
(
d
u
y
−
1
)
!
\frac{(n-2-D)!}{(du_1-1)!(du_2-1)!...(du_y-1)!}
(du1−1)!(du2−1)!...(duy−1)!(n−2−D)!
所以总共的方案数就是
(
n
−
2
−
D
)
!
(
d
u
1
−
1
)
!
(
d
u
2
−
1
)
!
.
.
.
(
d
u
y
−
1
)
!
x
n
−
2
−
D
\frac{(n-2-D)!}{(du_1-1)!(du_2-1)!...(du_y-1)!}x^{n-2-D}
(du1−1)!(du2−1)!...(duy−1)!(n−2−D)!xn−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;
}