zoukankan      html  css  js  c++  java
  • 【树】矩阵树定理

    矩阵树定理

    学习资料:OI Wiki

    用途与时间复杂度

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

    定义与做法

    无向图:

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

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

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

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

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

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

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

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

    其中记号 (L(G)egin{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_1lambda_2dotslambda_{n-1} ]

    有向图:

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

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

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

    [A_{ij}(G)=#e(i,j),\,i e 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)egin{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)egin{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_{vin 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
    ",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
    ",det(L));
    }
    

    扩展:最小生成树计数

    最小生成树有性质:

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

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

    做法:

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

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

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

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

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

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

    时间复杂度:

    (O(max(n^3logn,n imes 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
    ",res);
    }
    
  • 相关阅读:
    解决Maven下载依赖慢
    Spring Boot系列教程六:日志输出配置log4j2
    Spring Boot系列教程三:使用devtools实现热部署
    Spring Boot系列教程五:使用properties配置文件实现多环境配置
    Spring Boot系列教程四:配置文件详解properties
    Spring Boot系列教程二:创建第一个web工程 hello world
    Spring Boot系列教程一:Eclipse安装spring-tool-suite插件
    Spring Boot系列教程十:Spring boot集成MyBatis
    vim入门一 常用指令
    Linux IO多路复用之epoll网络编程(含源码)
  • 原文地址:https://www.cnblogs.com/kkkek/p/13696708.html
Copyright © 2011-2022 走看看