矩阵树定理
lichenyu_ac · · 个人记录
矩阵树定理
前言:欢迎来到线性代数最高城——Matrix-Tree定理。
写在矩阵树定理之前
行列式
定义一个
定义一个矩阵的行列式:
其中
如果直接做,容易发现时间复杂度为
考虑一些特殊矩阵,比如:
当P.S.其他的情况中间会乘上
此时,
考虑使用高斯消元法将
分别考虑高斯消元操作总对答案贡献的影响,设变换后的矩阵为
- 交换
A 中的两行。
容易发现加法乘法都有交换律,所以对于一个排列
更具体的,在数组
交换后的数组
显然,这是双射的,意思是这是一一对应的。
此时,每一个排列的逆序对数都乘上/除以
- 将其中一行乘上
K 。
此时每一个排列都有且仅有一个元素变为原来的
我们将
- 如果有两行完全相同,那么
|A|=0 。
运用性质1,我们有
所以
通过性质2推广,如果一行正好是另一行的
- 将其中一行加上另一行的
K 倍。
我们拆式子,发现我们相当于加上的一个新矩阵的行列式,这个新矩阵有一行正好是另一行的
所以新矩阵的贡献为
所以我们做一次高斯消元,同时维护行列式的值。容易发现,
矩阵树定理
给一张有向图
再定义邻接矩阵
矩阵树定理:以
当
这时每棵生成树的权值为边权的乘积。
证明不会。。。不过这里有可能详细的证明。
使用高斯消元法求解的时间复杂度为
应用
P6178 【模板】Matrix-Tree 定理
Solution
维护以一为根的外向生成树的权值,删去第一行第一列。
使用高斯消元法维护矩阵的行列式的值,时间复杂度为
代码如下:
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=310,mod=1e9+7;
ll power(ll a,ll b){
ll ans=1;
for(;b;b>>=1){
if(b&1)ans=ans*a%mod;
a=a*a%mod;
}
return ans;
}
int n,m,op;
ll A[N][N];
ll solve(){
int ans=1,opt=1;
for(int i=2,pos;i<=n;i++){
pos=i;
for(int j=i+1;j<=n;j++)
if(A[j][i]>A[pos][i])pos=j;
if(i!=pos)opt=-opt,swap(A[i],A[pos]);
if(!A[i][i])return 0;
for(int j=2;j<=n;j++){
if(j==i)continue;
ll tmp=A[j][i]*power(A[i][i],mod-2)%mod;
for(int k=2;k<=n;k++)
A[j][k]=(A[j][k]-tmp*A[i][k]%mod+mod)%mod;
}
ans=ans*A[i][i]%mod;
ll tmp=power(A[i][i],mod-2);
for(int j=2;j<=n;j++)A[i][j]=A[i][j]*tmp%mod;
}
return (ans*opt%mod+mod)%mod;
}
int main(){
scanf("%d%d%d",&n,&m,&op);
for(int i=1,x,y,z;i<=m;i++){
scanf("%d%d%d",&x,&y,&z);
if(op==0){
A[x][x]=(A[x][x]+z)%mod,A[y][y]=(A[y][y]+z)%mod;
A[x][y]=(A[x][y]-z+mod)%mod,A[y][x]=(A[y][x]-z+mod)%mod;
}else if(op==1){
A[y][y]=(A[y][y]+z)%mod;
A[x][y]=(A[x][y]-z+mod)%mod;
}
}
printf("%lld\n",solve());
return 0;
}
P3317 [SDOI2014] 重建
题意简述:每条边概率连通,求最求概率最小生成树。
Solution
由上面的例题我们容易发现,矩阵树定理求的是对于图上所有的生成树
通过概率的加法/乘法原理,我们将所有的生成树的概率加起来,一颗生成树的概率为
推推式子,将式子化为
容易发现
我们将边权设为
代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=70;
const double eps=1e-7;
double A[N][N],ans=1.0;
int n;
double solve(){
double res=1.0;
int opt=1;
for(int i=1;i<=n-1;i++){
int pos=i;
for(int j=i+1;j<=n-1;j++)
if(A[pos][i]<A[j][i])pos=j;
if(pos!=i)swap(A[i],A[pos]),opt=-opt;
if(fabs(A[i][i])<eps)return 0.0;
for(int j=i+1;j<=n-1;j++){
double tmp=A[j][i]/A[i][i];
for(int k=i;k<=n-1;k++)
A[j][k]-=tmp*A[i][k];
}
res=res*A[i][i];
for(int j=i;j<=n-1;j++)A[i][j]=A[i][j]/A[i][i];
}
return res*opt;
}
int main(){
scanf("%d",&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++){
if(fabs(A[i][j])<eps)A[i][j]=eps;
else if(fabs(1.0-A[i][j])<eps)A[i][j]=1.0-eps;
if(j>i)ans*=(1-A[i][j]);
A[i][j]=A[i][j]/(1-A[i][j]);
}
for(int i=1;i<=n;i++){
A[i][i]=0;
for(int j=1;j<=n;j++)if(i!=j){
A[i][i]+=A[i][j],A[i][j]=-A[i][j];
}
}
ans*=solve();
printf("%.10lf\n",ans);
return 0;
}
P4336 [SHOI2016] 黑暗前的幻想乡
给定一张图,边有颜色,求正好
Solution
容易发现,我们强制让
我们
于是,我们要让一个“至多”的条件变为严格“等于”的条件。
由于
时间复杂度为
代码如下:
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=30,mod=1e9+7;
ll power(ll a,ll b){
ll ans=1;
for(;b;b>>=1){
if(b&1)ans=ans*a%mod;
a=a*a%mod;
}
return ans;
}
ll A[N][N],ans;
int n;
vector<pair<ll,ll>>g[N];
ll solve(){
ll res=1,opt=1;
for(int i=1;i<=n;i++){
int pos=-1;
for(int j=i;j<=n;j++)
if(A[j][i]){pos=j;break;}
if(pos==-1)return 0;
if(i!=pos)opt=-opt,swap(A[i],A[pos]);
for(int j=1;j<=n;j++){
if(i==j)continue;
ll tmp=A[j][i]*power(A[i][i],mod-2)%mod;
for(int k=1;k<=n;k++)
A[j][k]=(A[j][k]-tmp*A[i][k]%mod+mod)%mod;
}
res=res*A[i][i]%mod;
ll tmp=power(A[i][i],mod-2);
for(int j=1;j<=n;j++)A[i][j]=A[i][j]*tmp%mod;
}
return (res*opt%mod+mod)%mod;
}
int main(){
scanf("%d",&n),n--;
for(int i=1,K;i<=n;i++){
scanf("%d",&K);
while(K--){
int x,y;scanf("%d%d",&x,&y);
g[i].push_back({x,y});
}
}
for(int i=1;i<1<<n;i++){
memset(A,0,sizeof(A));
int cnt=0;
for(int j=1;j<=n;j++){
if(!(i>>(j-1)&1))continue;
cnt++;
for(int k=0;k<g[j].size();k++){
int x=g[j][k].first,y=g[j][k].second;
A[x][y]++,A[y][x]++,A[x][x]--,A[y][y]--;
}
}
ans=((ans+((cnt&1)?-1:1)*solve())%mod+mod)%mod;
}
printf("%lld\n",ans);
return 0;
}