51NOD 1806 wangyurzee的树(容斥原理 + 组合数学)

来源:互联网 发布:组策略隐藏网络配置 编辑:程序博客网 时间:2024/05/22 02:18

传送门
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 个点的生成树的数量为 nn2
这里解释一下:底数是指 n 个点,指数是指 n 个点所对应的 prufer 序列有几个位置。
然后我们发现题目中给出的限制条件很少 m17,最多也就只有 2m 种状态,然后根据这些状态进行容斥。
那么我们假设选取了 x 个条件。
因为一个 n 的点的树所对应的 prufer 序列有 n2 个数值,然后每个数字出现的个数就是这个点的 度数-1。
那么我们现在先求出 x 个条件所对应的度数之和 sum=(vi1)
然后 sum 就对应了 prufer 序列中的 sum 个位置,所以从 n2 个位置里选 sum 个位置,方案数为 C(n2,sum)
现在就对 prufer 序列中已知的条件进行排列
现在对 sumv11 个位置进行组合(因为 每个数字都是一样的) 所以方案数为 C(sum,v11)
现在还剩 tmp=sumv1+1 个位置
然后现在对 tmpv21 个位置进行组合(因为 每个数字都是一样的) 所以方案数为 C(tmp,v21)

直到这选定 x 个条件全部判断完,然后在根据乘法原理,将所有的方法数乘起来。
现在还剩余 nx 个点,这些是没有限制的,然后就是 (nx)(n2sum)
将所有的乘起来之后就是如下公式:
ans=(n2)!(n2sum)!(vi1)!(nx)(n2sum)
然后相应的进行容斥,将最开始的总方案数算出来,然后奇减偶加。
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;}
原创粉丝点击