传送门
wangyurzee有n个各不相同的节点,编号从1到n。wangyurzee想在它们之间连n-1条边,从而使它们成为一棵树。
可是wangyurzee发现方案数太多了,于是他又给出了m个限制条件,其中第i个限制条件限制了编号为u[i]的节点的度数不能为d[i]。
一个节点的度数,就是指和该节点相关联的边的条数。
这样一来,方案数就减少了,问题也就变得容易了,现在请你告诉wangyurzee连边的方案总数为多少。
答案请对1000000007取模。
样例解释
总方案共有3种,分别为{(1,2),(1,3)},{(1,2),(2,3)},{(2,3),(1,3)}。其中第二种方案节点1的度数为2,不符合要求,因此答案为2。
Input
第一行输入2个整数n(1<=n<=1000000),m(0<=m<=17)分别表示节点个数以及限制个数。
第2行到第m+1行描述m个限制条件,第i+1行为2个整数u[i],d[i],表示编号为u[i]的节点度数不能为d[i]。
为了方便起见,保证1<=ui<=m。同时保证1<=ui<=n,1<=di<=n-1,保证不会有两条完全相同的限制。
Output
输出一行一个整数表示答案。
Input示例
3 1
1 2
Output示例
2
解题思路:
首先需要知道一个 prufer序列,具体详见prufer数列学习笔记
因为只要一个purfer序列确定了,那么由这个序列所组成的无根树也就确定了,所以只用算purfer序列的方案就可以了。
我们还需要知道的一个知识点就是:
n
个点的生成树的数量为
这里解释一下:底数是指
n
个点,指数是指
然后我们发现题目中给出的限制条件很少
m≤17
,最多也就只有
2m
种状态,然后根据这些状态进行容斥。
那么我们假设选取了
x
个条件。
因为一个
那么我们现在先求出
x
个条件所对应的度数之和
然后
sum
就对应了 prufer 序列中的
sum
个位置,所以从
n−2
个位置里选
sum
个位置,方案数为
C(n−2,sum)
现在就对 prufer 序列中已知的条件进行排列
现在对
sum
里
v1−1
个位置进行组合(因为 每个数字都是一样的) 所以方案数为
C(sum,v1−1)
现在还剩
tmp=sum−v1+1
个位置
然后现在对
tmp
里
v2−1
个位置进行组合(因为 每个数字都是一样的) 所以方案数为
C(tmp,v2−1)
…
直到这选定
x
个条件全部判断完,然后在根据乘法原理,将所有的方法数乘起来。
现在还剩余
将所有的乘起来之后就是如下公式:
ans=(n−2)!(n−2−sum)!∏(vi−1)!∗(n−x)(n−2−sum)
然后相应的进行容斥,将最开始的总方案数算出来,然后奇减偶加。
trick:这里有几个坑
1): 当只有一个点的时候,如果 m=1 直接输出 0,否则都是 1
2):有一种情况是一个点可能有多种限制情况,这样的需要考虑:
比如 :
n = 4 m = 2
1 1
1 2
代码:
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <math.h>
using namespace std;
typedef long long LL;
const LL MOD = 1e9+7;
const int MAXN = 20;
int u[MAXN], v[MAXN], vis[MAXN];
LL Pow(LL a, LL b){
LL ans = 1;
while(b){
if(b & 1) ans = ans * a % MOD;
b>>=1;
a = a * a % MOD;
}
return ans;
}
LL fac[1000005], Inv[1000005];
void Init(){
fac[0] = fac[1] = Inv[0] = Inv[1] = 1;
for(int i=2; i<1000005; i++) fac[i] = fac[i-1] * i % MOD;
for(int i=2; i<1000005; i++) Inv[i] = (MOD - MOD / i) * Inv[MOD % i] % MOD;
for(int i=2; i<1000005; i++) Inv[i] = Inv[i-1] * Inv[i] % MOD;
}
int main()
{
Init();
int n, m;
scanf("%d%d", &n, &m);
if(n == 1){
if(m == 1) {puts("0"); return 0;}
else {puts("1"); return 0;}
}
for(int i=0; i<m; i++) scanf("%d%d", &u[i], &v[i]);
LL ans = Pow(n, n-2);
int state = (1<<m);
int aa = 0;
for(int i=1; i<state; i++){
int cnt = 0, sum = 0;
memset(vis, 0, sizeof(vis));
LL t = 1;
for(int j=0; j<m; j++){
if(i & (1<<j)){
if(vis[u[j]]) goto A;
cnt++;
sum += (v[j]-1);
vis[u[j]] = 1;
t = t * Inv[v[j]-1] % MOD;
}
}
if(sum > n-2) { A:; continue; }
LL tmp = fac[n-2] * Inv[n-2-sum] % MOD;
tmp = tmp * Pow(n-cnt, n-2-sum) % MOD;
tmp = tmp * t % MOD;
if(cnt & 1) ans = (ans - tmp) % MOD;
else ans = (ans + tmp) % MOD;
}
ans = (ans % MOD + MOD) % MOD;
printf("%lld\n",ans);
return 0;
}