容斥原理。ios
首先看到題意,發現必然須要求一系列圖的生成樹數量,所以想到先引入矩陣樹定理來求解。spa
而後考慮如何求得知足限制的方案數。code
直接算須要考慮到以前哪一個公司已經修了邊,修了哪些邊,很難作。string
能夠容斥,先求出全部能夠造成的生成樹的總數。it
此時會發現明顯算多了,恰好由 n - 1 個公司建造的生成樹是統計上了,可是也統計上了恰好由 n - 2 個公司建造的生成樹。io
這時能夠枚舉究竟是哪 n - 2 個公司建造了這個樹,建圖的時候只加入這n-2個公司的邊,對着這個圖跑一邊矩陣樹,而後依次減去這些集合的方案數。ast
以此類推,設點集邊集造成的圖的集合爲 S,以下式。class
\(ans = \sum_{T \subseteq S} (-1)^{|T| + 1} * Matrix\_tree(T)\)。stream
很典型的容斥式子。原理
#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; }