最近几天讲图论,不得不猛搞,于是用了一两天时间:高斯消元 -> 行列式 -> Matrix-Tree定理 -> LGV引理
怕忘,写篇笔记。
高斯消元
一个用来解多元方程组的消元法。
就是以最常见的消元思路,从第一元到最后一元一个一个将除了本行系数以外的所有系数消为零,可以想象,如果我们将方程的系数用矩阵来表示,可能会长这样:
因为我们知道:
-
两方程交换,解不变
-
给一个方程的所有系数乘上 \(k\),解不变
-
将两个方程所有系数对应相加,解不变
每次消元,我们找到当前未知数所对应的列中最大的系数所在的行(也就是那个方程)交换到本行,再利用这个系数和上述第二条性质,把该未知数的其他系数消为 \(0\),那么最后一个方程在操作后一定形如 \(a_nx_n=b_n\),直接求解出 \(x_n\)
而此时原矩阵经过我们的操作一定会变成下三角矩阵,形如:
#include<bits/stdc++.h>
#define MAXN 102
using namespace std;
using db=double;
const db eps=1e-10;
int n;
db a[MAXN][MAXN];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n+1;j++)
cin>>a[i][j];
for(int i=1;i<=n;i++)
{
int max=i;
for(int j=i+1;j<=n;j++)
if(fabs(a[j][i])>fabs(a[max][i]))max=j;
for(int j=1;j<=n+1;j++)
swap(a[i][j],a[max][j]);
if(fabs(a[i][i])<eps){puts("No Solution");return 0;}
for(int j=1;j<=n;j++)
{
if(j==i)continue;
db tp=a[j][i]/a[i][i];
for(int k=i+1;k<=n+1;k++)
a[j][k]-=a[i][k]*tp;
}
}
for(int i=1;i<=n;i++)
printf("%.2lf\n",a[i][n+1]/a[i][i]);
return 0;
}
那么有了这个,我们能做什么呢?
行列式求值
行列式有一个很好的性质:
- 上三角/下三角矩阵的行列式等于对角线上值的乘积
而对于一个矩阵,
-
将矩阵的两行进行交换,其行列式的值会乘以 \(-1\)
-
将矩阵的一行乘以一个系数加给另一行,行列式的值不变
然后我们再一看高斯消元,这不刚好嘛!
于是我们可以在 \(\Theta(n^3+n^2logn)\) 的时间内求出行列式的值,而不是根据定义的 $\Theta(n\times n!) $ (雾
#include<bits/stdc++.h>
#define MAXN 602
using namespace std;
using ll=long long;
int n,mod;
struct Matrix
{
ll a[MAXN][MAXN];
inline ll*operator[](const int&x){return a[x];}
inline ll det()
{
ll res=1,f=1;
for(int i=1;i<=n;i++)
{
int max=i;
for(int j=i+1;j<=n;j++)
if(a[j][i]>a[max][i])max=j;
if(i!=max)swap(a[i],a[max]),f=-f;
if(!a[i][i])return 0;
for(int j=i+1;j<=n;j++)
{
if(a[j][i]>a[i][i])swap(a[i],a[j]),f=-f;
while(a[j][i])
{
ll l=a[i][i]/a[j][i];
for(int k=i;k<=n;k++)
a[i][k]=(a[i][k]-l*a[j][k]%mod+mod)%mod;
swap(a[i],a[j]),f=-f;
}
}
(res*=a[i][i])%mod;
}
return ((res%mod)*f+mod)%mod;
}
}m;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>mod;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>m[i][j];
printf("%lld\n",m.det());
return 0;
}
这些就是前置知识,接下来是图论内容。
Matrix-Tree 定理
这个定理既然叫 Matrix-Tree 自然是有原因的,它告诉了我们如何求一个图的生成树个数。
对于一张无向图,定义它的度数矩阵为 \(D\),邻接矩阵为 \(E\),此处度数矩阵的定义为:
然后可以由这两个矩阵相减得到新矩阵 \(K=D-E\)
那么这个无向图的生成树个数就是将 \(K\) 去掉一行一列后的行列式的值。
那有向图呢?
定义入度矩阵为 \(D_{in}\),出度矩阵为 \(D_{out}\)
同理相减可得 \(K_{in}=D_{in}-E\),\(K_{out}=D_{out}-E\)
那这个有向图的外向生成树的个数是 \(K_{in}\) 去掉一行一列后的行列式的值,内向生成树的个数是 \(K_{out}\) 去掉一行一列后的行列式的值。
那带权呢?
那么行列式的值就等于 \(\sum\limits_{T}\prod\limits_{i \in E} w_i\)
#include<bits/stdc++.h>
#define MAXN 302
using namespace std;
using ll=long long;
const int mod=1e9+7;
int n,m,t;
struct Matrix
{
ll a[MAXN][MAXN];
ll*operator[](int x){return a[x];}
inline Matrix operator-(Matrix b)const
{
Matrix res;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
res[i][j]=a[i][j]-b[i][j];
return res;
}
inline ll det()
{
ll res=1;int f=1;
for(int i=2;i<=n;i++)
{
int max=i;
for(int j=i+1;j<=n;j++)
if(a[j][i]>a[max][i])max=j;
if(i!=max)swap(a[i],a[max]),f=-f;
if(!a[i][i])return 0;
for(int j=i+1;j<=n;j++)
{
if(a[j][i]>a[i][i])swap(a[j],a[i]),f=-f;
while(a[j][i])
{
ll l=a[i][i]/a[j][i];
for(int k=i;k<=n;k++)
a[i][k]=(a[i][k]-l*a[j][k]%mod+mod)%mod;
swap(a[i],a[j]),f=-f;
}
}
(res*=a[i][i])%=mod;
}
return (res*f+mod)%mod;
}
}K;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>m>>t;
for(int i=1,u,v,w;i<=m;i++)
{
cin>>u>>v>>w;
if(t==0)
{
K[u][v]=(K[u][v]-w%mod+mod)%mod;
K[v][u]=(K[v][u]-w%mod+mod)%mod;
(K[u][u]+=w%mod)%=mod;
(K[v][v]+=w%mod)%=mod;
}
else
{
K[u][v]=(K[u][v]-w%mod+mod)%mod;
(K[v][v]+=w%mod)%=mod;
}
}
// for(int i=1;i<=n;i++)
// {
// for(int j=1;j<=n;j++)
// printf("%10lld ",K[i][j]);
// puts("");
// }
printf("%lld\n",K.det());
return 0;
}