Matrix-Tree 定理
Aaron_Romeo · · 算法·理论
Matrix-Tree 定理证明
跟 @whjhr 不同的容斥证明。
摘自与 Ender 的QQ聊天讨论记录。
七天内有效
拜谢 Ender
2024.1.5 update
@Judgelight 说觉得有点抽象,要求翻译一下:
思路
不妨设算的是 所有边从叶子连向根 的生成树 的个数。
所以相当于要给每个除根以外的点找一个其要连向的父亲(删掉一行一列的含义)
但是不是每种方案都是合法的。可以发现,当且仅当不存在环时方案合法。
考虑容斥:至少
实现
Kirchhoff 矩阵 中
考虑行列式计算中每个排列所代表的含义
如果第
否则第
相应的,第
而最终一定会回到
所以推出这个排列乘出来数的绝对值 是形成某些环的方案的总数
那正负号为啥正确呢?
我们期望的符号是跟环的数量定的,环有偶数个则为正, 环有奇数个则为负
而实际计算中,符号由两个因素影响。一是
其中
逆序对:
对于随便取的
对于环
总贡献为
综上得证
2024.1.7 update
因为 @nodgd 说可以 又去问了一波有了这个拓展,我愿称之为 exMatrix-Tree 定理 :
exMatrix-Tree 定理
发现给 Kirchhoff 矩阵的对角线每个数加一个变量
但不是很好算,因为多项式的消元要么变成实数域,要么整数辗转相除出不出来,比如
怎么办捏,发现此时将所有行(也可能是列,看怎么写的)相加并替换第一行(列)后全是
Code
#include <iostream>
typedef long long ll;
const ll N = 8;
ll read() {ll x; return scanf("%lld", &x), x;}
ll read1() {ll x; return scanf("%1lld", &x), x;}
ll a[N + 5][N + 5];
int main()
{
ll n = read();
for (ll i = 1; i <= n; i ++ )
for (ll j = 1; j <= n; j ++ )
{
ll u = j, v = i, w = read1();
a[u][u] += w, a[v][u] -= w;
}
for (ll i = 1; i <= n; i ++ ) a[1][i] = 1;
ll f = 1;
for (ll i = 1; i <= n; i ++ )
for (ll j = i + 1; j <= n; j ++ )
{
while (a[i][i])
{
ll t = a[j][i] / a[i][i];
for (ll k = i; k <= n; k ++ ) a[j][k] -= t * a[i][k];
std :: swap(a[i], a[j]), f = -f;
}
std :: swap(a[i], a[j]), f = -f;
}
ll res = f;
for (ll i = 1; i <= n; i ++ ) res *= a[i][i];
printf("%lld", res);
return 0;
}
2024.2.17 update
胡扯赛考到了 exMatrix-Tree,可以作为例题(
Code
#include <iostream>
#include <cstring>
#include <vector>
#include <cmath>
typedef long long ll;
const int N = 16;
const int M = 125;
const int mod = 1e9 + 7;
int read() {int x; return scanf("%d", &x), x;}
int u[M + 5], v[M + 5], w[M + 5];
int g[N + 5][N + 5];
int f[1 << N | 5], ans;
inline int det(int a[][N + 5], int n)
{
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
(a[i][j] < 0) && (a[i][j] += mod);
int res = 1;
for (int i = 1; i <= n; i ++ )
{
int t = i;
for (int j = i; j <= n; j ++ ) if (a[j][i]) {t = j; break;}
if (!a[t][i]) return 0;
if (t != i) res = mod - res, std :: swap(a[i], a[t]);
for (int j = i + 1; j <= n; j ++ )
{
int x = i, y = j;
while (a[y][i])
{
int t = mod - a[x][i] / a[y][i];
for (int k = i; k <= n; k ++ ) a[x][k] = (a[x][k] + 1ll * a[y][k] * t) % mod;
std :: swap(x, y);
}
if (x != i) std :: swap(a[x], a[i]), res = mod - res;
}
res = 1ll * res * a[i][i] % mod;
}
return res;
}
int main()
{
int n = read(), m = read();
for(int i = 1; i <= m; i ++ ) u[i] = read(), v[i] = read(), w[i] = read();
for(int i = 0; i < 1 << n - 1; i ++ )
{
std :: memset(g, 0, sizeof g);
for(int j = 1; j <= m; j ++ )
if (i >> w[j] & 1)
g[u[j]][v[j]] -- , g[v[j]][v[j]] ++ ;
for (int j = 1; j <= n; j ++ ) g[1][j] = 1;
f[i] = det(g, n);
for (int j = i & (i - 1); j; j = j - 1 & i) (f[i] += mod - f[j]) >= mod && (f[i] -= mod);
int t = 0;
while (i >> t & 1) t ++ ;
ans = (ans + 1ll * f[i] * t) % mod;
}
printf("%d\n", ans);
return 0;
}