容斥原理。
首先看到题意,发现必然需要求一系列图的生成树数量,因此想到先引入矩阵树定理来求解。
然后考虑如何求得满足限制的方案数。
直接算需要考虑到之前哪个公司已经修了边,修了哪些边,很难做。
可以容斥,先求出所有可以形成的生成树的总数。
此时会发现明显算多了,刚好由 n - 1 个公司建造的生成树是统计上了,但是也统计上了刚好由 n - 2 个公司建造的生成树。
这时可以枚举到底是哪 n - 2 个公司建造了这个树,建图的时候只加入这n-2个公司的边,对着这个图跑一边矩阵树,然后依次减去这些集合的方案数。
以此类推,设点集边集形成的图的集合为 S,如下式。
\(ans = \sum_{T \subseteq S} (-1)^{|T| + 1} * Matrix\_tree(T)\)。
很典型的容斥式子。
#include <cmath>
#include <queue>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define u first
#define v second
const int maxn = 20;
const int mod = 1000000000 + 7;
int n, m[maxn], a[maxn][maxn], ans;
std::vector<pair<int, int> > edge[maxn];
inline int Fast_pow(int x, int p) {
int res = 1;
for( ; p; x = 1ll * x * x % mod, p = p >> 1) if( p & 1 ) res = 1ll * x * res % mod;
return res;
}
inline int Gauss(int len) {
int res = 1;
for(int i = 1; i <= len; ++i) for(int j = i + 1; j <= len; ++j) {
if( a[i][i] == 0 ) return 0;
if( a[j][i] ) {
int tmp = 1ll * a[j][i] * Fast_pow(a[i][i], mod - 2) % mod;
for(int k = i; k <= len; ++k) a[j][k] = (a[j][k] - 1ll * tmp * a[i][k] % mod + mod) % mod;
}
}
for(int i = 1; i <= len; ++i) res = 1ll * res * a[i][i] % mod;
return res;
}
int main(int argc, char const *argv[])
{
scanf("%d", &n);
for(int i = 1; i < n; ++i) {
scanf("%d", m + i);
for(int x, y, j = 1; j <= m[i]; ++j) scanf("%d%d", &x, &y), edge[i].push_back(make_pair(x, y));
}
for(int f = 0, i = 0; i < (1 << (n - 1)); ++i) {
memset(a, 0, sizeof a), f = 1;
for(int j = 1; j < n; ++j) if( i & (1 << (j - 1)) ) {
for(int k = 0; k < m[j]; ++k) {
--a[edge[j][k].u][edge[j][k].v], --a[edge[j][k].v][edge[j][k].u];
++a[edge[j][k].u][edge[j][k].u], ++a[edge[j][k].v][edge[j][k].v];
}
} else f = -f;
for(int j = 1; j <= n; ++j) for(int k = 1; k <= n; ++k) if( a[j][k] < 0 ) a[j][k] = a[j][k] + mod;
ans = ((ans + f * Gauss(n - 1)) % mod + mod) % mod;
}
printf("%d\n", ans);
return 0;
}