矩阵树定理初识

额,好像东西有点多,这个接受能力有点弱,让我娓娓道来......

示范博客:

这个(分三部分梳理有向图无向图)

这个(很全)

这个(拓展到位)

行列式

首先给出定义式吧!

\[det(K)=\sum_p(-1)^{\tau(P)}\prod\limits_{i=1}^{n}K_{i,p_i} \]

\(P\)就是一个排列,这里枚举所有的排列,这就是定义我也不知道为什么......

然后这个东东有好多奇奇怪怪的性质,不要背哦,要理解!!

性质1

两个矩阵相加得到的矩阵的行列式不等于两个矩阵的行列式相加

自己摸一个样例就知道了

性质2

虽然不满足性质1,但是如果两个矩阵只有一行不一样,那么这两个矩阵的行列式相加等于这两个矩阵相同行继承不同行相加的矩阵的行列式

换句话说就是,若行列式的某一行每一个元素都可以由两个数相加得到,则这个行列式是对应两个行列式的和

性质3

一个矩阵的行列式和它的转置的行列式相同

任意两个数的大小和位置关系没有变,所以行列式不变

性质4

交换矩阵中的两行,行列式变成相反数

这个好证明,我们知道一个排列中交换两个数,逆序对变化是奇数的,所以符号会变化

性质5

某一行的所有数都乘\(k\),相当于对行列式乘\(k\)

直接看定义式就能明白

性质6

如果矩阵中有两行数成比例,那么行列式等于0

先给小的乘\(k\),使这两行数相等,然后交换,交换相等的两行行列式不变,但是交换了就会取相反数

一个数等于自己的相反数,也只有0了

性质7

把行列式的某一行的各元素乘以同一数然后加到另一行对应的元素上去,行列式不变

高斯消元法求解

这个可以由性质2得到,相当于拆出来一个矩阵,而这个矩阵中有两行是相等的,所以拆出来的矩阵行列式为0

那么我们就可以用屌丝消元来求解行列式了

因为消元之后行列式不变,而我每次交换的时候就变成相反数就好了

如果有逆元的话,消的时候可以直接乘逆元,如果没有就可以辗转相除

辗转相除的原理就是,性质6,加上一个数的几倍不变,减去也不变,所以我可以不断用小的来消大的

别忘了辗转相除的时候,交换两行要把行列式变成相反数

逆元法
for(int h=1,z=1;h<=n&&z<=n;h++,z++){
    int mx=h;
    fo(i,h+1,n)if(d[i][z]>d[mx][z])mx=i;
    if(mx!=h){
        ans=-ans;
        fo(i,z,n)swap(d[mx][i],d[h][i]);
    }
    if(!d[h][z]){h--;continue;}
    fo(i,h+1,n)if(d[i][z]){
        int t=d[i][z]*ksm(d[h][z],mod-2)%mod;
        fo(j,z,n)d[i][j]=(d[i][j]-d[h][j]*t%mod+mod)%mod;
    }
}
fo(i,1,n)ans=(ans*d[i][i]%mod+mod)%mod;
printf("%lld",(ans+mod)%mod);
辗转相除法
for(int h=2,z=2;h<=cnt&&z<=cnt;h++,z++){
    int mx=h;
    fo(i,h+1,cnt)if(a[i][z]>a[mx][z])mx=i;
    if(mx!=h){ans=-ans;fo(i,z,cnt)swap(a[mx][i],a[h][i]);}
    if(!a[h][z]){h--;continue;}
    fo(i,h+1,cnt){
        while(a[i][z]){
            int t=a[h][z]/a[i][z];
            fo(j,z,cnt)a[h][j]=(a[h][j]-a[i][j]*t%mod+mod)%mod,swap(a[h][j],a[i][j]);
            ans=-ans;
        }
    }
}
fo(i,2,cnt)ans=ans*a[i][i]%mod;
printf("%lld",(ans+mod)%mod);

这里的高斯消元可以简化,不需要这么多东西

基尔霍夫Kirchhoff矩阵

这个矩阵再加上上面行列式的东西,就是矩阵树定理的全部内容

这玩意是建立在图上的。。。

首先说这个啥啥矩阵的构造吧:可以用度数矩阵(D)-邻接矩阵(A)

度数矩阵只有对角线有值,\(a[i][i]\)表示链接\(i\)的边有几条(这是在无向图上的定义)

邻接矩阵就是\(a[i][j]\)表示从\(i\)\(j\)的边有几条,无向图的\(a[i][j]==a[j][i]\)

然后这个东西有一个非常美好的性质:一张图的生成树个数,就是这东西的任意一个\(n-1\)阶主子式的行列式(注意这里没有绝对值)

啥叫\(i\)阶主子式?就是挑出来\(i\)\(i\)列,注意是相同的编号,这些行列的交点的矩阵

那在这里就随便去掉一行和编号相同的一列就行了,去掉第一行比较省事......

那这个定理就说完了,然而还有一些意想不到的东西!!

上面说的只是无向图的无根树生成树个数

带权值的话?

这个简单,我们可以当做权值就是这样的边的个数

我们就直接多加这么多条边就好了

那么我们的生成树的权值乘积之和就是把权值当成边的个数得到的生成树个数

在一个形态中,这样的生成树个数就是每一条边的个数乘积,这个和权值正好对应

那么我们可以推广矩阵树定理求的就是\(\sum\limits_{T}\prod\limits_{e \in T}p_e\)

据说这东西叫变元矩阵树定理

\(T\)表示一颗生成树的边集,\(p_e\)就表示\(e\)这条边的权值,于是上式就是所有生成树的权值乘积之和

有向图求内向树的话?

那我们的度数矩阵就变成出度,就是从当前点指向别的点的边的数量

邻接矩阵还是正常的邻接矩阵\(a[i][j]\)表示从\(i\)\(j\)的边有多少

一个重点,如果规定了根是谁的话,我的只能删掉根的那一行一列

也就是\(n-1\)阶主子式不能包含根那一行一列

有向图求外向树的话?

度数矩阵是入度,从别的点指向当前点

邻接矩阵正常

一个重点,如果规定了根是谁的话,我的只能删掉根的那一行一列

也就是\(n-1\)阶主子式不能包含根那一行一列

有向图带权?

直接多加几条就好了!!!

Luogu6178 Matrix-Tree 定理

code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
int read(){
    int s=0,t=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
    return s*t;
}
const int mod=1e9+7;
const int N=305;
const int M=1e5+5;
int ksm(int x,int y){
    int ret=1;
    while(y){
        if(y&1)ret=ret*x%mod;
        x=x*x%mod;y>>=1;
    }return ret;
}
int n,m,t,ans=1;
int d[N][N],a[N][N];
signed main(){
    // cout<<"shit"<<endl;
    n=read();m=read();t=read();
    // cout<<"fuck"<<endl;
    fo(i,1,m){
        int u=read(),v=read(),w=read();
        a[u][v]=(a[u][v]+w)%mod;d[v][v]=(d[v][v]+w)%mod;
        if(t==0){
            a[v][u]=(a[v][u]+w),d[u][u]=(d[u][u]+w)%mod;
        }
    }
    // cout<<"ZZ"<<endl;
    fo(i,1,n)fo(j,1,n)d[i][j]=(d[i][j]-a[i][j]+mod)%mod;
    fo(i,1,n)d[i][1]=d[1][i]=0;
    // cout<<"SV"<<endl;
    for(int h=2,z=2;h<=n&&z<=n;h++,z++){
        int mx=h;
        fo(i,h+1,n)if(d[i][z]>d[mx][z])mx=i;
        if(mx!=h){
            ans=-ans;
            fo(i,z,n)swap(d[mx][i],d[h][i]);
        }
        if(!d[h][z]){h--;continue;}
        fo(i,h+1,n)if(d[i][z]){
            int t=d[i][z]*ksm(d[h][z],mod-2)%mod;
            fo(j,z,n)d[i][j]=(d[i][j]-d[h][j]*t%mod+mod)%mod;
        }
    }
    fo(i,2,n)ans=(ans*d[i][i]%mod+mod)%mod;
    printf("%lld",(ans+mod)%mod);
}

例题

Luogu4336 SHOI2016 黑暗前的幻想乡

牛逼容斥题,发现我们不能直接求,所以就容斥......

code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
int read(){
    int s=0,t=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
    return s*t;
}
const int mod=1e9+7;
const int N=20;
int ksm(int x,int y){
    int ret=1;
    while(y){
        if(y&1)ret=ret*x%mod;
        x=x*x%mod;y>>=1;
    }return ret;
}
int n,d[N][N],ans;
int e[N][N*N][2];
void add(int x,int y){d[x][x]++;d[y][y]++;d[x][y]--;d[y][x]--;}
int gs(){
    int ret=1;
    fo(i,2,n){
        fo(j,i+1,n)if(d[j][i]){fo(k,i,n)swap(d[j][k],d[i][k]);ret=(mod-ret)%mod;break;}
        if(!d[i][i])continue;
        fo(j,i+1,n){
            if(!d[j][i])continue;
            int t=d[j][i]*ksm(d[i][i],mod-2)%mod;
            fo(k,i,n)d[j][k]=(d[j][k]-d[i][k]*t%mod)%mod;
        }
    }
    fo(i,2,n)ret=ret*d[i][i]%mod;
    return (ret+mod)%mod;
}
signed main(){
    n=read();
    fo(i,1,n-1){
        e[i][0][0]=read();
        fo(j,1,e[i][0][0])e[i][j][0]=read(),e[i][j][1]=read();
    }
    fo(s,1,(1<<n-1)-1){
        fo(i,1,n)fo(j,1,n)d[i][j]=0;
        int sum=0;
        fo(i,1,n-1){
            if(!((s>>i-1)&1))continue;
            sum++;
            fo(j,1,e[i][0][0]){
                int x=e[i][j][0],y=e[i][j][1];
                add(x,y);
            }
        }
        fo(i,1,n)fo(j,1,n)d[i][j]=(d[i][j]%mod+mod)%mod;
        int res=gs()%mod;
        if(n-1-sum&1)ans=(ans-res+mod)%mod;
        else ans=(ans+res)%mod;
    }
    printf("%lld",ans);
}

Luogu4208 JSOI2008 最小生成树计数

最小生成树性质的再次应用,发现权值互不影响

我们可以将某一权值拆出来,因为将某一权值的边全部删去之后,连通性不变

所以我们可以用乘法原理,直接把每一种权值的方案乘起来

每一中权值的方案数就是矩阵树定理求的

code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define fo(i,x,y) for(int i=(x);i<=(y);i++)
#define fu(i,x,y) for(int i=(x);i>=(y);i--)
int read(){
    int s=0,t=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')t=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
    return s*t;
}
const int mod=31011;
const int N=105;
const int M=1005;
int n,m,d[N][N],fai[N],ans=1;
int find(int x){return fai[x]==x?x:fai[x]=find(fai[x]);}
void init(int n){fo(i,1,n)fai[i]=i;}
struct E{int x,y,v;}e[M];
bool com(E a,E b){return a.v<b.v;}
bool vis[M];map<int,int> mp;
int ys[N],cnt;
void add(int x,int y){d[x][x]++;d[y][y]++;d[x][y]--;d[y][x]--;}
int get(int o){
    int ret=1;
    fo(i,1,n)ys[i]=0;cnt=0;init(n);
    fo(i,1,m)if(vis[i]&&e[i].v!=o)fai[find(e[i].x)]=e[i].y;
    fo(i,1,n)if(fai[i]==i)ys[i]=++cnt;
    fo(i,1,cnt)fo(j,1,cnt)d[i][j]=0;
    fo(i,1,m)if(e[i].v==o)add(ys[find(e[i].x)],ys[find(e[i].y)]);
    fo(i,2,cnt){
        fo(j,i+1,cnt)if(d[j][i]){ret=(mod-ret)%mod;fo(k,i,cnt)swap(d[j][k],d[i][k]);}
        if(!d[i][i])continue;
        fo(j,i+1,cnt){
            while(d[j][i]){
                int t=d[i][i]/d[j][i];
                fo(k,i,cnt)d[i][k]=(d[i][k]-d[j][k]*t%mod+mod)%mod,swap(d[i][k],d[j][k]);
                ret=(mod-ret)%mod;
            }
        }
    }
    fo(i,2,cnt)ret=ret*d[i][i]%mod;
    return (ret+mod)%mod;
}
signed main(){
    n=read();m=read();
    fo(i,1,m)e[i].x=read(),e[i].y=read(),e[i].v=read();
    init(n);sort(e+1,e+m+1,com);
    fo(i,1,m){
        int fx=find(e[i].x),fy=find(e[i].y);
        if(fx==fy)continue;
        vis[i]=true;fai[fx]=fy;mp[e[i].v]=1;
    }
    for(pair<int,int> i:mp)ans=ans*get(i.first)%mod;
    printf("%lld",ans);
}
posted @ 2021-12-22 20:00  fengwu2005  阅读(157)  评论(0编辑  收藏  举报