【树】矩阵树定理

矩阵树定理

学习资料:OI Wiki

用途与时间复杂度

用于求图(有向图和无向图)的生成树个数计数问题。时间复杂度为 \(\mathcal{O(n^3)}\) ,其中 \(n\) 是图的节点个数。需要掌握求矩阵行列式的方法。

定义与做法

无向图:

定义度数矩阵 \(D(G)\)

\[D_{i,i}(G)=deg(i),\;D_{i,j}(G)=0,\,i\ne j \]

\(\#e(i,j)\) 为点 \(i\) 与点 \(j\) 相连的边数,定义邻接矩阵 \(A(G)\) :

\[A_{ij}(G)=A_{ji}(G)=\#e(i,j),\,i\ne j \]

定义 Laplace 矩阵(亦称 Kirchhoff 矩阵) \(L(G)\) 为:

\[L(G)=D(G)-A(G) \]

\(G\) 生成树个数 \(t(G)\)

\[t(G)=det\,L(G)\begin{pmatrix} 1,2,\dots,i-1,i+1,\dots n\\ 1,2,\dots,i-1,i+1,\dots n \end{pmatrix} \]

其中记号 \(L(G)\begin{pmatrix} 1,2,\dots,i-1,i+1,\dots n\\ 1,2,\dots,i-1,i+1,\dots n \end{pmatrix}\) 表示矩阵 \(L(G)\) 的第 \(1,\dots,i-1,i+1,\dots n\) 行与第 \(1,\dots,i-1,i+1,\dots n\) 列构成的子矩阵。

\(t(G)\)\(L(G)\) 少去任意一点构成的方阵的行列式的值相等。

\(\lambda_1,\lambda_2,\dots,\lambda_{n-1}\)\(L(G)\)\(n-1\) 个非零特征值,那么有:

\[t(G)=\frac1{n}\lambda_1\lambda_2\dots\lambda_{n-1} \]

有向图:

定义度数矩阵 \(D^{out}(G)\) , \(D^{in}(G)\)

\[D_{i,i}^{out}(G)=deg^{out}(i),\;D_{i,j}^{out}(G)=0,\,i\ne j \\ D_{i,i}^{in}(G)=deg^{in}(i),\;D_{i,j}^{in}(G)=0,\,i\ne j \]

\(\#e(i,j)\) 为点 \(i\) 指向点 \(j\) 的边数,定义邻接矩阵 \(A(G)\) :

\[A_{ij}(G)=\#e(i,j),\,i\ne j \]

定义 Laplace 矩阵(亦称 Kirchhoff 矩阵) \(L^{out}(G)\) , \(L^{out}(G)\) 为:

\[L^{out}(G)=D^{out}(G)-A(G)\\ L^{in}(G)=D^{in}(G)-A(G) \]

\(t^{root}(G,k)\) 表示以 \(k\) 为根,且所有边均指向父亲的树形图个数。

\(t^{leaf}(G,k)\) 表示以 \(k\) 为根,且所有边均指向子节点的树形图个数。

根向行树形图个数 \(t^{root}(G,k)\)

\[t^{root}(G,k)=det\,L^{out}(G)\begin{pmatrix} 1,2,\dots,k-1,k+1,\dots n\\ 1,2,\dots,k-1,k+1,\dots n \end{pmatrix} \]

叶向形树形图个数 \(t^{leaf}(G,k)\)

\[t^{leaf}(G,k)=det\,L^{in}(G)\begin{pmatrix} 1,2,\dots,k-1,k+1,\dots n\\ 1,2,\dots,k-1,k+1,\dots n \end{pmatrix} \]

BEST定理:

\(G\) 是有向欧拉图,那么 \(G\) 的不同欧拉回路总数 \(ec(G)\) 是:

\[ec(G)=t^{root}(G,k)\prod_{v\in V}(deg(v)-1)! \]

对欧拉图 \(G\) 的任意两个节点 \(k,k'\) ,都有 \(t^{root}(G,k)=t^{root}(G,k')\) ,且欧拉图 \(G\) 的所有节点的入度和出度相等。

例题

1, luoguP4111 小z的房间

无向图生成树计数模板题。

#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define mkp(a,b) make_pair(a,b)
using namespace std;
typedef long long ll;
const int mod=1e9;
char s[15][15];
int id[110][110];
int D[110][110],A[110][110];ll L[110][110];
int n;
ll det(ll a[][110])
{
    ll det=1,t;
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            while(a[j][i]){
                t=a[i][i]/a[j][i];
                for(int k=i;k<=n;k++)
                    a[i][k]=(a[i][k]-t*a[j][k]%mod+mod)%mod;
                swap(a[i],a[j]);
                det*=-1;
            }
        }
        if(!a[i][i])return 0;
        else det=det*a[i][i]%mod;
    }
    return (det+mod)%mod;
}

int main()
{
    int nn,mm,cnt=0,tid;
    scanf("%d%d",&nn,&mm);
    for(int i=1;i<=nn;i++)
        scanf("%s",s[i]+1);
    for(int i=1;i<=nn;i++)
        for(int j=1;j<=mm;j++){
            if(s[i][j]!='.')continue;
            id[i][j]=++cnt;
            if(s[i-1][j]=='.'){
                tid=id[i-1][j];
                D[cnt][cnt]++;D[tid][tid]++;
                A[cnt][tid]++;A[tid][cnt]++;
            }
            if(s[i][j-1]=='.'){
                tid=id[i][j-1];
                D[cnt][cnt]++;D[tid][tid]++;
                A[cnt][tid]++;A[tid][cnt]++;
            }
        }
    for(int i=1;i<=cnt;i++) {
        for(int j=1;j<=cnt;j++) {
            L[i][j]=D[i][j]-A[i][j];
        }
    }
    n=cnt-1;
//    for(int i=1;i<=n;i++){
//        for(int j=1;j<=n;j++)printf("%2lld ",L[i][j]);putchar(10);
//    }
    printf("%lld\n",det(L));
}

2,luoguP4455 社交网络

定根有向树形图计数模板题

#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define mkp(a,b) make_pair(a,b)
using namespace std;
typedef long long ll;
const int mod=1e4+7;
const int N=330;
int L[N][N];
int n;
int kpow(int a,int b){
    int ans=1;
    while(b){
        if(b&1)ans=ans*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ans;
}
inline int ni(int a){return kpow(a,mod-2);}
int det(int A[][N])
{
    int k;
    int temp,det=1,nii;
    for(int i=2;i<=n;i++)
    {
        k=i;
        for(int j=i+1;j<=n;j++)
            if(A[j][i])k=j;
        if (k!=i)det*=-1;
        swap(A[i],A[k]);
        if(A[i][i]==0)return 0;
        det=det*A[i][i]%mod;
        temp=A[i][i];
        nii=ni(temp);
        for(int j=2;j<=n;j++)A[i][j]=A[i][j]*nii%mod;
        for(int j=2;j<=n;j++){
            if(j==i)continue;
            temp=A[j][i];
            for(int k=2;k<=n;k++)
                A[j][k]=(A[j][k]-A[i][k]*temp%mod+mod)%mod;
        }
    }
    return (det+mod)%mod;
}
int main()
{
    int m,u,v;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&u,&v);
        L[u][u]++;L[v][u]--;
    }
    printf("%d\n",det(L));
}

扩展:最小生成树计数

最小生成树有性质:

  • 同一个图的每个最小生成树中,边权相等的边数量相等。

  • 不同生成树中,某一种权值的边任意加入需要的数量后,形成的联通块状态是一样的。

做法:

  • 设图的边权集合为 \(E\)

  • 首先求出一棵最小生成树 \(T\),然后找到 \(T\) 的边权不重集合 \(S\) ,大小为 \(cntv\)

  • 枚举 \(S\) 的边权 \(w_i\),对 \(T\) 中边权不等于 \(w_i\) 的边所连的点联通压缩;

  • 对所有的点以联通块为单位,枚举 \(E\) 中边权为 \(w_i\) 的边 \(e\),将 \(e\) 所连接的点(即点所在的联通块)加入新图中。

    • 注意:连通块的编号要从 \(1\) 开始连续编号,设连通块个数为 \(cntb\) ,则新图的点的个数的为 \(cntb\) ,矩阵的大小即为 \(cntb\times cntb\)。因为如果直接按原图的父亲节点加入的话,新图的点的个数为\(n\) ,矩阵的大小则为 \(n\times n\) , 新图无法找到它的最小生成树(即无法使所有点联通,因为某些点已经被压缩,即在矩阵中度数为 \(0\)) 。
  • 求新图的生成树个数 \(num_i\)

  • 则原图最小生成树个数为 \(\prod_{i=1}^{cntv}num_i\)

时间复杂度:

\(O(max(n^3logn,n\times m))\) 。 其中的 \(n^2logn\) 是用辗转相除法的求行列式

例题:

P4208最小生成树计数

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int maxn=5e3+5;
const int mod=31011;

//最小生成树计数 
struct Eg{
	int u,v,w;
	bool operator<(const Eg&eg)const{return w<eg.w;}
}eg[maxn],eg1[maxn];
int pre[maxn];
int find(int x)
{
	int r=x,t;
	while(r!=pre[r])r=pre[r];
	while(x!=r){
		t=pre[x];pre[x]=r;x=t;
	}
	return r;
}
int val[maxn],cntv,cnte;
int L[110][110];
int det(int a[][110],int n)
{
    int det=1,t;
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            while(a[j][i]){
                t=a[i][i]/a[j][i];
                for(int k=i;k<=n;k++)
                    a[i][k]=(a[i][k]-t*a[j][k]%mod+mod)%mod;
                swap(a[i],a[j]);
                det*=-1;
            }
        }
        if(!a[i][i])return 0;
        else det=det*a[i][i]%mod;
    }
    return (det+mod)%mod;
}
int bk[110],cntb;
int main()
{
	int n,m,u,v,w;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&u,&v,&w);
		eg[i]={u,v,w};
	}
	sort(eg+1,eg+1+m);
	for(int i=1;i<=n;i++)pre[i]=i;
	for(int i=1;i<=m;i++)
	{
		u=eg[i].u;v=eg[i].v;w=eg[i].w;
		if(find(u)==find(v))continue;
		if(w!=val[cntv])val[++cntv]=w;
		pre[find(u)]=find(v);
		eg1[++cnte]={u,v,w};
	}
	if(cnte!=n-1){
		puts("0");return 0;
	}
	int res=1;
	for(int i=1;i<=cntv;i++)
	{
		for(int j=1;j<=n;j++)pre[j]=j;
		for(int j=1;j<=cnte;j++)
			if(eg1[j].w!=val[i])pre[find(eg1[j].u)]=find(eg1[j].v);
		cntb=0;
		for(int j=1;j<=n;j++) 
			if(find(j)==j)bk[j]=++cntb;
		for(int j=1;j<=n;j++)
			for(int k=j;k<=n;k++)
				L[j][k]=L[k][j]=0;
		for(int j=1;j<=m;j++)
		{
			if(eg[j].w==val[i]){
				u=bk[find(eg[j].u)];v=bk[find(eg[j].v)];
				L[u][u]++;L[v][v]++;
				L[u][v]--;L[v][u]--;
			}
		}
		res=res*det(L,cntb-1)%mod;
	}
	printf("%d\n",res);
}
posted @ 2020-09-19 16:19  草丛怪  阅读(271)  评论(0编辑  收藏  举报