生成树计数 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;
MST计数的话,如果同边权边较少的话可以考虑用暴力。它基于MST的两条性质:
- 每种权值的边的数量是固定的
- 不同的生成树中,某一种权值的边任意加入需要的数量后,形成的联通块状态是相同的
意思就是对于所有的MST对于边权例如为w的边的数量是一定的。这就启发我们暴力搜索每种边权选的使用的边,然后用乘法原理计算出方案数。这种办法因为是枚举所有的使用同边权使用情况所以时间复杂度是O(2^k*m)(k是同边权边数)。但是因为这个解法有局限性所以也不是特别有用。
去掉同边权边数限制的话就是更广泛的最小生成树计数了,要用到缩点+Matrix-Tree定理的解法。具体就是还是枚举每个边权i,然后把非边权i的边加入到图中然后缩点,对缩完点之后的图求Kirchhoff 矩阵利用Matrix-Tree定理求出生成树个数那么这个就是边权i的方案数了,然后全部边权乘法原理乘起来就是答案了。
给出一幅图求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;
以上是关于生成树计数 Matrix-Tree 定理的主要内容,如果未能解决你的问题,请参考以下文章
康复计划#5 Matrix-Tree定理(生成树计数)的另类证明和简单拓展
bzoj1016-JSOI2008 最小生成树计数 最小生成树 dfs/matrix-tree定理