生成树计数 Matrix-Tree 定理

Posted clno1

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了生成树计数 Matrix-Tree 定理相关的知识,希望对你有一定的参考价值。

一直都知道要用Matrix-Tree定理来解决生成树计数问题,但是拖到今天才来学。博主数学不好也只能跟着各位大佬博客学一下它的应用以及会做题,证明实在是不会。

推荐博客https://blog.csdn.net/u011815404/article/details/89091011(Matrix-Tree定理)

https://blog.csdn.net/u011815404/article/details/99679527(写得无敌好的生成树计数了)

 

那么比较常见的生成树计数问题就三种:①生成树计数②同边权边较少的MST计数③同边权边较少的MST计数,针对这3个问题有3种解决办法。

最简单的生成树计数就是Matrix-Tree定理的模板题啦,直接求出Kirchhoff 矩阵然后求它的n-1阶行列式绝对值。

Highways

代码抄袭参考的大佬的。

技术图片
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=10+5;
int n,m;

LL K[N][N];
LL det(int n)  //求矩阵K的n-1阶顺序主子式
    LL ret=1;
    for(int i=1;i<=n-1;i++)  //枚举主对角线上第i个元素
        for(int j=i+1;j<=n-1;j++)  //枚举剩下的行
            while(K[j][i])  //辗转相除
                int t=K[i][i]/K[j][i];
                for(int k=i;k<=n-1;k++)  //转为倒三角
                    K[i][k]=K[i][k]-t*K[j][k];
                swap(K[i],K[j]);  //交换i、j两行
                ret=-ret;  //取负
            
        
        ret=ret*K[i][i];
    
    return abs(ret);


int main()

    int T; cin>>T;
    while (T--) 
        scanf("%d%d",&n,&m);
        memset(K,0,sizeof(K));
        for (int i=1;i<=m;i++) 
            int x,y; scanf("%d%d",&x,&y);
            K[x][x]++; K[y][y]++;
            K[x][y]--; K[y][x]--;
        
        printf("%lld\n",det(n));
    
    return 0;
 
View Code

 

MST计数的话,如果同边权边较少的话可以考虑用暴力。它基于MST的两条性质:

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

意思就是对于所有的MST对于边权例如为w的边的数量是一定的。这就启发我们暴力搜索每种边权选的使用的边,然后用乘法原理计算出方案数。这种办法因为是枚举所有的使用同边权使用情况所以时间复杂度是O(2^k*m)(k是同边权边数)。但是因为这个解法有局限性所以也不是特别有用。

去掉同边权边数限制的话就是更广泛的最小生成树计数了,要用到缩点+Matrix-Tree定理的解法。具体就是还是枚举每个边权i,然后把非边权i的边加入到图中然后缩点,对缩完点之后的图求Kirchhoff 矩阵利用Matrix-Tree定理求出生成树个数那么这个就是边权i的方案数了,然后全部边权乘法原理乘起来就是答案了。

 

洛谷P4208 最小生成树计数

给出一幅图求MST数量,缩点+Matrix-Tree定理解决。(这道题的数据也可以用暴力搜索解决)

代码还没写这里先贴个大佬的模板:

技术图片
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
#include<bitset>
#define EPS 1e-9
#define PI acos(-1.0)
#define INF 0x3f3f3f3f
#define LL long long
#define Pair pair<int,int>
const int MOD = 31011;
const int N = 1000+5;
const int dx[] = 0,0,-1,1,-1,-1,1,1;
const int dy[] = -1,1,0,0,-1,1,-1,1;
using namespace std;
 
 
struct Edge 
    int x,y;
    int dis;
    bool operator < (const Edge &rhs) const 
        return dis<rhs.dis;
    
 edge[N],tr[N];
int n,m;
int father[N];
int G[N][N];
int tot,bel[N],val[N];
int Find(int x) 
    if(father[x]!=x)
        return father[x]=Find(father[x]);
    return x;

int Gauss(int n) 
    int res=1;
    for(int i=1; i<=n; i++) 
        for(int k=i+1; k<=n; k++) 
            while(G[k][i]) 
                int d=G[i][i]/G[k][i];
                for(int j=i; j<=n; j++)
                    G[i][j]=(G[i][j]-1LL*d*G[k][j]%MOD+MOD)%MOD;
                swap(G[i],G[k]);
                res=-res;
            
        
        res=1LL*res*G[i][i]%MOD,res=(res+MOD)%MOD;
    
    return res;

int Kruskal() 
    sort(edge+1,edge+m+1);
    for(int i=1; i<=n; i++)
        father[i]=i;
    int cnt=0;
    for(int i=1; i<=m; i++) 
        int fu=Find(edge[i].x);
        int fv=Find(edge[i].y);
        if(fu==fv)
            continue;
        father[fu]=fv,tr[++cnt]=edge[i];
        if(edge[i].dis!=val[tot])
            val[++tot]=edge[i].dis;
    
    return cnt;

void addTreeEdge(int v) 
    for(int i=1; i<n&&tr[i].dis!=v; i++)
        int x=tr[i].x;
        int y=tr[i].y;
        father[Find(x)]=Find(y);
    
    for(int i=n-1; i&&tr[i].dis!=v; i--)
        int x=tr[i].x;
        int y=tr[i].y;
        father[Find(x)]=Find(y);
    

void rebuild(int v) 
    memset(G,0,sizeof(G));
    for(int i=1; i<=m; i++)
        if(edge[i].dis==v)
            int x=bel[edge[i].x];
            int y=bel[edge[i].y];
            G[x][y]--;
            G[y][x]--;
            G[x][x]++;
            G[y][y]++;
        
    

int main() 
    scanf("%d%d",&n,&m);
    for(int i=1; i<=m; i++)
        scanf("%d%d%d",&edge[i].x,&edge[i].y,&edge[i].dis);
 
    int cnt=Kruskal();
    if(cnt!=n-1) 
        printf("0\n");
    
    else
        int res=1;
        for(int i=1; i<=tot; i++) 
            for(int i=1; i<=n; i++)
                father[i]=i;
 
            addTreeEdge(val[i]);
 
            int blo=0;
            for(int i=1; i<=n; i++)
                if(Find(i)==i)
                    bel[i]=++blo;
            for(int i=1; i<=n; i++)
                bel[i]=bel[Find(i)];
 
            rebuild(val[i]);
            res=1LL*res*Gauss(blo-1)%MOD;
        
        printf("%d\n",res);
    
    return 0;
View Code

 

以上是关于生成树计数 Matrix-Tree 定理的主要内容,如果未能解决你的问题,请参考以下文章

康复计划#5 Matrix-Tree定理(生成树计数)的另类证明和简单拓展

bzoj1016-JSOI2008 最小生成树计数 最小生成树 dfs/matrix-tree定理

BZOJ 1016 [JSOI2008]最小生成树计数 ——Matrix-Tree定理

Matrix-Tree 定理(基尔霍夫矩阵树定理)

Matrix-Tree 定理(基尔霍夫矩阵树定理)

uva 10766 Organising the Organisation 生成树计数