线性代数、矩阵树定理

矩阵求逆

我们现在求逆 \(A\),那么我们把单位矩阵 \(I\) 放在 \(A\) 的右边,然后对这个 \((n\times 2n)\) 的矩阵消元。
我们得到了一个形如 \([IB]\) 的矩阵,\(B\) 就是 \(A\) 的逆矩阵。
如左边得不到 \(I\),那么就无解。

行列式求值

根据定义 $\det A=|A|=\sum_p (-1)^{\tau(p)}\prod A_{i,p_i} $,其中 \(\tau (p)\) 指排列 \(p\) 的逆序对个数。
考虑优化计算,有几个性质:

  1. 若某行乘上 \(t\)\(|A|\) 增到到 \(t\) 倍。
  2. 交换矩阵两行,\(|A|\) 变号。
  3. 有某两行一样的矩阵,\(|A|=0\)
  4. 用矩阵的一行加上另一行的倍数,\(|A|\) 不变。

于是我们可以利用高消,将矩阵消成上三角矩阵或只有对角线非零的矩阵,行列式为对角线之积。

矩阵树定理

\(K_{i,j}=\left\{\begin{matrix}\sum_{i\neq k} w_{i,k} & (i=j)\\-w_{i,j}&(i\neq j)\\\end{matrix}\right.\)
\(M\) 表示 \(K\) 删去任意编号相同的一行和一列的矩阵,
\(|M|=\sum_{t\in T} \prod_{(u,v)\in t} w_{u,v}\).

证明的话,我们选一个点钦定为根,并忽略它(不忽略连向其的边),也就是删去的那个编号。
剩下每个点随便连边,形成树,或者形成基环树森林+树。所以我们枚举环的个数进行容斥。
注意到一个排列可以被表示成若干轮换的形式,所以实际上我们相当于枚举排列。
\(C(p)\) 表示排列 \(p\) 的环的个数,答案是 \(\sum_p (-1)^{C(p)}\prod M_{i,p_i}\)
发现这个式子与行列式很相似,其次 \(\tau\)\(C\) 其实是相关的,于是证出。

code
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
const int N=305,mod=1e9+7;
int n,m,t,A[N][N];
int inv(int a) {
	int b=mod-2,res=1;
	for(; b; b>>=1,a=1ll*a*a%mod) 
		if(b&1) res=1ll*res*a%mod;
	return res;
}
int main() {
	scanf("%d%d%d",&n,&m,&t); n--;
	for(int i=1; i<=m; i++) {
		int u,v,w; scanf("%d%d%d",&u,&v,&w);
		u--; v--;
		if(t==0) {
			A[u][u]=(A[u][u]+w)%mod;
			A[v][v]=(A[v][v]+w)%mod;
			A[u][v]=(A[u][v]-w+mod)%mod;
			A[v][u]=(A[v][u]-w+mod)%mod;
		} else {
			A[v][v]=(A[v][v]+w)%mod;
			A[v][u]=(A[v][u]-w+mod)%mod;
		}
	}
	int res=1;
	for(int i=1; i<=n; i++) {
		int tmp=inv(A[i][i]);
		for(int j=i+1; j<=n; j++) {
			int rat=1ll*A[j][i]*tmp%mod;
			for(int q=i; q<=n; q++)
				A[j][q]=(A[j][q]-1ll*rat*A[i][q]%mod+mod)%mod;
		}
		res=1ll*res*A[i][i]%mod;
	}
	printf("%d\n",res);
	return 0;
}

生成树计数:把权值设为 \(1\) 即可。
外向树计数:把边先反向,再把给定的那个根忽略掉即可。
生成树权值和:
stupid 的做法:拆贡献,算边在多少树中出现,容斥:矩阵树求全部边的方案数-减掉这条边的方案数。
smart 的做法:把 \((u,v,w)\) 列一个生成函数 \(wx+1\),取模 \(x^2\) 即可。

P3317 [SDOI2014] 重建

我们要求的是 \(\sum_{t\in T} \prod_{(u,v)\in t} w_{u,v}\prod_{(u,v)\notin t} (1-w_{u,v})\).
\(=\sum_{t\in T} \prod_{(u,v)\in t} w_{u,v}\dfrac{\prod (1-w_{u,v})}{\prod_{(u,v)\in t} (1-w_{u,v})}\).
所以令 \(w=\dfrac{w}{1-w}\),结果乘上 \(\prod (1-w_{u,v})\) 即可。
直接套用矩阵树定理。

P6624 [省选联考 2020 A 卷] 作业题

考虑枚举 \(\gcd\),把这个 \(\gcd\) 的倍数的边都提取出来跑一遍矩阵树定理(\(wx+1\))。
得出每个 \(\gcd\) 的答案后考虑容斥,这样是 \(O(n^3V+V\ln V)\) 的。
发现只有边数不小于 \(n-1\)\(\gcd\) 才有效,发现能跑过。
事实上,不用容斥可以用欧拉反演,也就是 \(\sum_T (\sum w)\times \sum _{d|\forall w_i} \varphi (d)\).
\(=\sum_d \varphi(d)\ \sum_{T,d|\forall w} \times \sum_{d|w}w\).

P4336 [SHOI2016] 黑暗前的幻想乡

考虑容斥,设 \(f(i)\) 表示钦定只用 \(i\) 个公司的答案。那么答案就是 \(\sum_{i=0}^{n-1} (-1)^{n-1-i}f(n)\).
状压枚举所有选的公司的状态,每个状态跑一遍矩阵树即可。\(O(2^nn^3)\),

P4208 [JSOI2008] 最小生成树计数

考虑模拟 kruskal 进行的过程,考虑当前加到大小为 \(w\) 的边。
当前势必已近连成若干连通块,加上大小为 \(w\) 的边就会连接这些连通块成为森林,
森林里每个树都跑一遍矩阵树即可。

BEST 定理

对于一个包含欧拉回路的有向图,设 \(T_s\) 表示 \(s\) 为根内向生成树个数,
那么欧拉回路的数量(除循环同构)为 \(T_s \prod (od_i-1)!\),其中 \(od\) 表示出度。

证明:首先有,每个点最后一条出边构成一个根的内向树。
每个点由于固定了一条出边,所以贡献是 \((od_i-1)!\)
对于起始点 \(s\),随便走一条,贡献是 \(od_s!\),由于要除循环同构,故除以 \(od_s\)

实现:矩阵树定理。

P7531 [USACO21OPEN] Routing Schemes P

考虑如果建出源点和汇点,源点连 sender,receiver 连汇点,汇点到源点连 \(s\) 条边。
那么就变成一个欧拉回路计数问题。由于源点和汇点出边顺序不重要,所以答案除以 \((s!)^2\)

posted @ 2024-03-10 20:59  GloriousCc  阅读(3)  评论(0编辑  收藏  举报