矩阵树定理
用于求解图上的生成树个数。生成树个数等于基尔霍夫矩阵任何一个 N − 1 N-1 N−1阶主子式的行列式的绝对值。
基尔霍夫矩阵构造方法
A A A为邻接矩阵, D D D为度数矩阵,基尔霍夫(Kirchhoff)矩阵为 K = D − A K=D-A K=D−A。
即 a [ i ] [ i ] a[i][i] a[i][i]为点 i i i的度数, a [ i ] [ j ] a[i][j] a[i][j]为 i i i, j j j之间边数的相反数。
例子:
行列式求法
高斯消元化为上三角矩阵,其行列式值为对角线的乘积。
若题目要求取模,高斯消元时使用辗转相除。(辗转相除代码参考例题)
变元矩阵树定理
求所有生成树总边积的和。行列式 a [ i ] [ i ] a[i][i] a[i][i]记录点 i i i边权和, a [ i ] [ j ] a[i][j] a[i][j]记录 i i i, j j j之间边权的相反数。
例题
bzoj4031 HEOI2015 小Z的房间
矩阵树模板题。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll ;
const int mod = 1e9 ;
int tot ;
int s[15][15] ;
ll f[110][110] ;
int dx[4] = {0, 0, 1, -1}, dy[4] = {1, -1, 0, 0} ;
void add (int u, int v) {
if (u > v) return ;
f[u][u] ++; f[v][v] ++ ;
f[u][v] --; f[v][u] -- ;
}
ll gauss () {
ll ans = 1 ;
for (int i = 1; i < tot; i ++) {
for (int j = i + 1; j < tot; j ++) {
while (f[j][i]) {
ll t = f[i][i] / f[j][i] ;
for (int k = i; k < tot; k ++)
f[i][k] = (f[i][k] - t * f[j][k] + mod) % mod ;
swap (f[i], f[j]) ;
ans = -ans ;
}
}
ans = (ans * f[i][i]) % mod ;
}
return (ans + mod) % mod ;
}
int main() {
int n, m ;
cin >> n >> m ;
for (int i = 1; i <= n; i ++) {
char tmp[15] ;
scanf("%s", tmp + 1) ;
for (int j = 1; j <= m; j ++)
if (tmp[j] == '.') s[i][j] = ++ tot ;
}
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= m; j ++) {
for (int d = 0; d < 4; d ++) {
int x = i + dx[d], y = j + dy[d] ;
if (s[i][j] && s[x][y]) add (s[i][j], s[x][y]) ;
}
}
printf("%lld\n", gauss ()) ;
return 0 ;
}
bzoj4596 SHOI2016 黑暗前的幻想乡
矩阵树定理 + 容斥
合法方案数 = 总方案数 - 一个未修建方案数 + 两个修建方案数…
#include <bits/stdc++.h>
#define fi first
#define se second
using namespace std;
typedef long long ll ;
typedef pair<int, int> P ;
const int mod = 1e9 + 7 ;
inline int read () {
int x = 0, f = 1; char c; c = getchar() ;
while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar(); }
while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar() ;
return x * f ;
}
vector<P> road[20] ;
int n ;
ll f[20][20] ;
void add (int u, int v) {
f[u][u] ++; f[v][v] ++ ;
f[u][v] --; f[v][u] -- ;
}
ll gauss () {
ll ans = 1 ;
for (int i = 1; i < n; i ++) {
for (int j = i + 1; j < n; j ++) {
while (f[j][i]) {
ll t = f[i][i] / f[j][i] ;
for (int k = i; k < n; k ++)
f[i][k] = (f[i][k] - t * f[j][k] % mod + mod) % mod ;
swap (f[i], f[j]) ;
ans = -ans ;
}
}
ans = ans * f[i][i] % mod ;
if (ans == 0) return 0 ;
}
return (ans + mod) % mod ;
}
int main() {
n = read () ;
for (int i = 1; i < n; i ++) {
int m = read () ;
for (int j = 1; j <= m; j ++) {
int u = read (), v = read () ;
road[i].push_back (P (u, v)) ;
}
}
ll ans = 0 ;
for (int i = 1; i < (1 << (n - 1)); i ++) {
memset (f, 0, sizeof f) ;
int cnt = 0 ;
for (int j = 1; j < n; j ++) {
if (i & (1 << (j - 1))) {
cnt ++ ;
for (int k = 0; k < road[j].size(); k ++)
add (road[j][k].fi, road[j][k].se) ;
}
}
if ((n - 1 - cnt) & 1) ans = (ans - gauss() + mod) % mod ;
else ans = (ans + gauss ()) % mod ;
}
cout << ans << endl ;
return 0 ;
}
bzoj3534 SDOI2014 重建
矩阵树可求 ∑ T ∏ e ∈ T p e \sum_T \prod_{e\in T}p_e ∑T∏e∈Tpe
此题求 ∑ T ( ∏ e ∈ T p e ∏ e ∉ T ( 1 − p e ) ) \sum_T (\prod_{e \in T}p_e \prod_{e \notin T}(1-p_e)) ∑T(∏e∈Tpe∏e∈/T(1−pe))
∑ T ( ∏ e ∈ T p e ∏ e ∉ T ( 1 − p e ) ) = ∑ T ( ∏ e ∈ T p e ∏ e ( 1 − p e ) ∏ e ∈ T ( 1 − p e ) ) = ∏ e ( 1 − p e ) ( ∑ T ∏ e ∈ T p e 1 − p e ) \sum_T (\prod_{e \in T}p_e \prod_{e \notin T}(1-p_e)) = \sum_T(\prod_{e \in T}p_e \frac{\prod_e(1-p_e)}{\prod_{e \in T}(1-p_e)})=\prod_e(1-p_e)(\sum_T \prod_{e \in T} \frac{p_e}{1-p_e}) ∑T(∏e∈Tpe∏e∈/T(1−pe))=∑T(∏e∈Tpe∏e∈T(1−pe)∏e(1−pe))=∏e(1−pe)(∑T∏e∈T1−pepe)
令矩阵树中 p e = p e 1 − p e p_e = \frac {p_e} {1-p_e} pe=1−pepe,求解。
#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-6 ;
int n ;
double a[55][55], ans = 1.0 ;
double gauss () {
double res = 1 ;
for (int i = 1; i < n; i ++) {
int mx = i ;
for (int j = i + 1; j < n; j ++)
if (fabs (a[j][i]) > fabs (a[mx][i])) mx = j ;
if (mx != i) swap (a[i], a[mx]) ;
for (int j = i + 1; j < n; j ++) {
double t = a[j][i] / a[i][i] ;
for (int k = i; k < n; k ++)
a[j][k] -= t * a[i][k] ;
}
res *= a[i][i] ;
}
return fabs (res) ;
}
int main() {
cin >> n ;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++)
scanf("%lf", &a[i][j]) ;
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++) {
double t = 1.0 - a[i][j] < eps ? eps : (1.0 - a[i][j]) ;
if (i < j) ans *= t ;
a[i][j] = a[i][j] / t ;
}
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++) {
if (i != j) {
a[i][i] += a[i][j]; a[i][j] = -a[i][j] ;
}
}
printf("%.8lf\n", ans * gauss()) ;
return 0 ;
}