矩阵树定理 Matrix Tree

  

​  矩阵树定理主要用于图的生成树计数。

  

  看到给出图求生成树的这类问题就大概要往这方面想了。

  

  算法会根据图构造出一个特殊的基尔霍夫矩阵\(A\),接着根据矩阵树定理,用\(A\)计算出生成树个数。

  

  

  

1.无向图的生成树计数

  

  对于给定的可含重边的连通无向图\(G\),求其生成树的个数。求法如下:

  

  定义度数矩阵\(D\):该矩阵仅在对角线上有值,\(D_{i,i}\)表示\(i\)号点的度数。对于图中每一条无向边\((u,v)\),\(D_{u,u}\)++,\(D_{v,v}\)++。

  

  定义邻接矩阵\(C\):\(C_{i,j}\)表示\(i\)到\(j\)的边数。对于图中每一条无向边\((u,v)\),\(C_{u,v}\)++,\(C_{v,u}\)++。

  

  定义图\(G\)的基尔霍夫矩阵\(A=D-C\)。

  

​  矩阵树定理:将\(A\)去掉第\(i\)行和第\(i\)列(\(i\in[1,n]\)),将它当做一个行列式求解,则\(\det(A)\)就是生成树个数。

  

    

  

2.有向图的树形图计数

  

  对于有向图,不存在“生成树”的概念,但存在“树形图”的概念。有向图中,若选定一个点作为树根,能构造出一棵“树”(包含\(n-1\)条边)使得根能到达任意节点,则这是一棵外向树;若能构造出一棵“树”使得任意节点能到达根,则这是一棵内向树。

  

  定义度数矩阵\(D\):该矩阵仅在对角线上有值,\(D_{i,i}\)表示\(i\)号点的度数。

  

           对于图中每一条有向边\((u,v)\),若构造外向树则\(D_{v,v}\)++;若构造内向树则\(D_{u,u}\)++。

  

  定义邻接矩阵\(C\):\(C_{i,j}\)表示\(i\)到\(j\)的边数。对于图中每一条有向边\((u,v)\),\(C_{u,v}\)++。

  

​  定义图\(G\)的基尔霍夫矩阵\(A=D-C\)。

  

  矩阵树定理:将\(A\)去掉第\(i\)行和第\(i\)列(\(i\in[1,n]\)),将它当做一个行列式求解,则\(\det(A)\)就是以\(i\)为根的外向/内向树形图个数。很多时候我们会发现\(A\)的对角线上某数为\(A_{i,i}=0\),删去第\(i\)行和第\(i\)列可以干掉0。只有这样行列式才不等于0,其实也就是说只能从\(i\)出发有解了。

  

   

  

3.细节

  

  求行列式的方法是:将行列式通过行列式初等变换消成上三角。此时对角线乘积即为行列式的值。

  

  注意,矩阵树定理这一套算法会考虑如何把所有的参与点构建成生成树,所以编号不要跳跃。如果说有障碍之类的元素,千万不要在矩阵中给它留一行一列,因为这一行一列都一定是0,算法会尝试将“障碍”构建进生成树,最后只能得到无解。

  

  

  

  

  

一些例题

  

BZOJ 4031

  

  传送门在此

  

  这是一道无向图生成树计数的裸题,直接上基础算法即可。

  

  这里就要注意障碍的处理了。我们应该对非障碍格子重新标号使得它们的编号连续,保证算法正常进行。

  

  这题的模数真的恶心,不是质数,没法用逆元。所以使用类辗转相除法来消元,每行的消元从\(O(n)\)变成\(O(n\lg )\)。

  

#include <cstdio>
using namespace std;
const int N=10,MOD=1e9;
int n,m,id[N][N],idcnt,a[N*N][N*N];
char map[N][N];
inline void swap(int &x,int &y){x^=y^=x^=y;}
inline int plus(int x,int y){return (x+y)%MOD;}
inline int mul(int x,int y){return 1LL*x*y%MOD;}
inline bool ok(int x,int y){return 1<=x&&x<=n&&1<=y&&y<=m&&map[x][y]=='.';}
void addEdge(int u,int v){
a[u][u]++; a[v][v]++;
a[u][v]--; a[v][u]--;
}
int solve(){
if(idcnt==1) return 1;
int all=idcnt-1,res=1;
for(int i=1;i<=all;i++)
for(int j=i+1;j<=all;j++)
while(a[j][i]){
int t=a[i][i]/a[j][i];
for(int k=i;k<=all;k++){
int q=plus(a[i][k],-mul(a[j][k],t));
a[i][k]=a[j][k];
a[j][k]=q;
}
res=-res;
}
for(int i=1;i<=all;i++) res=mul(res,a[i][i]);
return (res+MOD)%MOD;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%s",map[i]+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(map[i][j]=='.') id[i][j]=++idcnt;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(ok(i,j)){
if(ok(i+1,j))
addEdge(id[i][j],id[i+1][j]);
if(ok(i,j+1))
addEdge(id[i][j],id[i][j+1]);
}
printf("%d\n",solve());
return 0;
}

  

  

  

  

  

BZOJ 4894

  

  传送门

  

  这是一道有向图树形图计数。要求以1号点为根的外向树形图个数。

  

  按照上述做法直接写即可。删去\(A\)的第1行第1列,因为1号点没有入边,若不删第一行第一列行列式值为0,无法计算。

  

#include <cstdio>
using namespace std;
const int N=305,MOD=1e9+7;
int n,a[N][N];
char str[N];
inline int mul(int x,int y){return 1LL*x*y%MOD;}
inline int plus(int x,int y){return (x+y)%MOD;}
inline void swap(int &x,int &y){x^=y^=x^=y;}
int ksm(int x,int y){
int res=1;
for(;y;x=mul(x,x),y>>=1)
if(y&1) res=mul(res,x);
return res;
}
int gaussian(){
int res=1,size=n-1;
for(int i=1;i<size;i++){
if(!a[i][i]){
int l;
for(l=i+1;l<=size;l++)
if(a[l][i]) break;
if(l<=size&&a[l][i]){
for(int j=i;j<=size;j++) swap(a[l][j],a[i][j]);
res=-res;
}
else return 0;
}
for(int j=i+1;j<=size;j++){
int t=mul(a[j][i],ksm(a[i][i],MOD-2));
for(int k=i;k<=size;k++)
a[j][k]=plus(a[j][k],-mul(a[i][k],t));
}
}
for(int i=1;i<=size;i++) res=mul(res,a[i][i]);
return plus(res,MOD);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%s",str+1);
for(int j=1;j<=n;j++)
if(str[j]=='1')
a[j][j]++,a[i][j]--;
}
for(int i=1;i<n;i++)
for(int j=1;j<n;j++) a[i][j]=a[i+1][j+1];
printf("%d\n",gaussian());
return 0;
}

  

  

  

  

  

BZOJ 4596

  

  传送门

  

​  我开始不会做了。

  

​  我们发现如果将所有公司提供的边都加进图中,然后求生成树个数,是无法限制“每个公司至少要建一条”这个条件的。有的生成树可能只有一部分公司参与,比如说某一种生成树只含有\(S\)集合的公司。

  

  如果仅加入\(S\)集合所含公司的边,我们发现这些答案也会被统计到。既然有重复统计,可以考虑消除吗?

  

​  于是容斥的思想就体现出来了!

  

  我们枚举加入哪一些公司,分别求生成树个数。记参与公司集合为\(S\)时生成树个数为\(f(S)\),记有\(x\)个公司参与时的生成树总方案为\(中有个A_x=\sum_{S中有x个}f(S)\),则

\[Ans=A_n-A_{n-1}+A_{n-2}-A_{n-3}......
\]

​  复杂度为\(O(2^nn^3)\),其实是可以跑的过的。

  

#include <cstdio>
#include <vector>
#define mp make_pair
#define pb push_back
using namespace std;
typedef pair<int,int> pii;
const int N=18,MOD=1e9+7;
int n;
int a[N][N];
vector<pii> l[N];
vector<int> b[N];
inline bool in(int i,int j){return (i>>(j-1))&1;}
inline int plus(int x,int y){return (x+y)%MOD;}
inline int mul(int x,int y){return 1LL*x*y%MOD;}
inline void swap(int &x,int &y){x^=y^=x^=y;}
void clear_mat(){
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) a[i][j]=0;
}
void add_company(int id){
for(int i=0,sz=l[id].size();i<sz;i++){
int u=l[id][i].first,v=l[id][i].second;
a[u][u]++; a[v][v]++;
a[u][v]--; a[v][u]--;
}
}
int ksm(int x,int y){
int res=1;
for(;y;x=mul(x,x),y>>=1)
if(y&1) res=mul(res,x);
return res;
}
int gaussian(){
int s=n-1,res=1;
for(int i=1;i<=s;i++){
if(!a[i][i]){
int t;
for(t=i+1;t<=s&&!a[t][i];t++);
if(t>s) return 0;
for(int j=i;j<=s;j++) swap(a[i][j],a[t][j]);
res=-res;
}
int inv=ksm(a[i][i],MOD-2);
for(int j=i+1;j<=s;j++){
int t=mul(a[j][i],inv);
for(int k=i;k<=s;k++)
a[j][k]=plus(a[j][k],-mul(a[i][k],t));
}
}
for(int i=1;i<=s;i++) res=mul(res,a[i][i]);
return res;
}
int main(){
scanf("%d",&n);
for(int i=1,m;i<n;i++){
scanf("%d",&m);
for(int j=1,u,v;j<=m;j++){
scanf("%d%d",&u,&v);
l[i].pb(mp(u,v));
}
}
int all=1<<(n-1);
for(int i=1;i<all;i++){
int cnt=0;
for(int j=1;j<=n;j++)
cnt+=in(i,j);
b[cnt].pb(i);
}
int ans=0;
for(int i=n-1,r=1;i>=1;i--,r=-r)
for(int j=0,sz=b[i].size();j<sz;j++){
clear_mat();
int st=b[i][j];
for(int c=1;c<n;c++)
if(in(st,c)) add_company(c);
ans=plus(ans,gaussian()*r);
}
ans=plus(ans,MOD);
printf("%d\n",ans);
return 0;
}

  

  

  

总结

  

  矩阵树定理本身还是挺简单的,但愿自己不要忘得太快......

  

  但是要灵活运用(废话)。

  

​  如果要深入透彻,我还是得研究一下矩阵树定理的证明。不过就当一个大坑先留着吧。

【Learning】矩阵树定理 Matrix-Tree的更多相关文章

  1. 矩阵树定理(Matrix Tree)学习笔记

    如果不谈证明,稍微有点线代基础的人都可以在两分钟内学完所有相关内容.. 行列式随便找本线代书看一下基本性质就好了. 学习资源: https://www.cnblogs.com/candy99/p/64 ...

  2. @总结 - 7@ 生成树计数 —— matrix - tree 定理(矩阵树定理)与 prüfer 序列

    目录 @0 - 参考资料@ @0.5 - 你所需要了解的线性代数知识@ @1 - 矩阵树定理主体@ @证明 part - 1@ @证明 part - 2@ @证明 part - 3@ @证明 part ...

  3. 【算法】Matrix - Tree 矩阵树定理 & 题目总结

    最近集中学习了一下矩阵树定理,自己其实还是没有太明白原理(证明)类的东西,但想在这里总结一下应用中的一些细节,矩阵树定理的一些引申等等. 首先,矩阵树定理用于求解一个图上的生成树个数.实现方式是:\( ...

  4. 2018.09.16 spoj104Highways (矩阵树定理)

    传送门 第一次写矩阵树定理. 就是度数矩阵减去邻接矩阵之后得到的基尔霍夫矩阵的余子式的行列式值. 这个可以用高斯消元O(n3)" role="presentation" ...

  5. luoguP3317 [SDOI2014]重建 变元矩阵树定理 + 概率

    首先,我们需要求的是 $$\sum\limits_{Tree} \prod\limits_{E \in Tree} E(u, v) \prod\limits_{E \notin Tree} (1 - ...

  6. BZOJ3534 [Sdoi2014]重建 【矩阵树定理】

    题目 T国有N个城市,用若干双向道路连接.一对城市之间至多存在一条道路. 在一次洪水之后,一些道路受损无法通行.虽然已经有人开始调查道路的损毁情况,但直到现在几乎没有消息传回. 辛运的是,此前T国政府 ...

  7. [专题总结]矩阵树定理Matrix_Tree及题目&题解

    专题做完了还是要说两句留下什么东西的. 矩阵树定理通俗点讲就是: 建立矩阵A[i][j]=edge(i,j),(i!=j).即矩阵这一项的系数是两点间直接相连的边数. 而A[i][i]=deg(i). ...

  8. Wannafly挑战赛23F-计数【原根,矩阵树定理,拉格朗日插值】

    正题 题目链接:https://ac.nowcoder.com/acm/contest/161/F 题目大意 给出\(n\)个点的一张图,求它的所有生成树中权值和为\(k\)的倍数的个数.输出答案对\ ...

  9. [spoj104][Highways] (生成树计数+矩阵树定理+高斯消元)

    In some countries building highways takes a lot of time... Maybe that's because there are many possi ...

随机推荐

  1. SpringCloud 学习(二) :服务注册与发现Eureka

    Spring Cloud应用中可以支持多种的服务治理框架,比如Eureka.Consul.Zookeeper等,现在我们用的是consul,本文以SpringCloud Dalston.SR5版本介绍 ...

  2. Netty源码分析第8章(高性能工具类FastThreadLocal和Recycler)---->第2节: FastThreadLocal的set方法

    Netty源码分析第八章: 高性能工具类FastThreadLocal和Recycler 第二节: FastThreadLocal的set方法 上一小节我们学习了FastThreadLocal的创建和 ...

  3. Workbook对象的方法总结(一)

    import openpyxlwb=openpyxl.Workbook()print('1.添加前所有工作簿的名称是:',wb.get_sheet_names())wb.create_sheet('F ...

  4. 用Solidity在Truffle上构建一个HelloWorld智能合约

    原文地址:石匠的blog Truffle的环境安装在前文已经整理,这次用Solidity在Truffle写一个HelloWorld以太坊智能合约,并在testrpc上进行测试.当前的软件版本信息如下: ...

  5. mybatis学习----------查询数据库返回结果为空

    用mybits查询数据库时,如果参数已传入sql,sql也已经执行了,但是返回结果为空,首先保证数据库中有对应数据,如果有对应数据仍返回null,是数据库配置文件有问题.解决方案如下:1.mapper ...

  6. AutoResetEvent 方法名称设计缺陷

    这个类和方法,让人乍一读是读不明白的.不能通过方法名称明白其含义.所以它的方法名称设计是欠考虑. 应该类似于这样: public static class MyAutoResetEvent { pub ...

  7. resize2fs命令详解

    基础命令学习目录首页 原文链接:http://blog.51cto.com/woyaoxuelinux/1870299   resize2fs:调整ext文件系统的空间大小  搭配逻辑卷lv使用方法: ...

  8. fetch err : "Body not allowed for GET or HEAD requests"

    在使用 fetch 的时候 报了  "Body not allowed for GET or HEAD requests" 这个错. 代码如下: 一番google , 找到答案了. ...

  9. Xcode中的文件类型

    文件类型 Xcode中的文件类型,总共4种类型: 1 普通文件(File) 2 Group(在Xcode中就是黄色的文件夹) 3 Folder(在Xcode中就是蓝色的文件夹) 4 Framework ...

  10. 查询数据库时mapper报错:It's likely that neither a Result Type nor a Result Map was specified.

    因为mapper.xml里把resultType写成了parameterType