矩阵树定理 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. ngxin 添加模块

    if test -n "$NGX_ADDONS"; then echo configuring additional modules for ngx_addon_dir in $N ...

  2. 在虚拟机上搭建物理机可访问的web服务(IIS)

    0x0 前言 安装webug4.0的时候突发奇想,想学下如何在虚拟机里搭建网站,然后让主机像访问互联网的网站一样访问虚拟机的网站,为以后渗透测试搭建环境做准备 0x1 虚拟机安装win2003[以防万 ...

  3. wifi,Android渗透之arp欺骗

    查看自己wifi ip段 查看有哪些用户连接了此wifi,下图标记处为我的测试机(华为) 攻击开始,如果开启了arp防火墙,就会有提示 开启图片捕获

  4. TIME_WAIT 你好!

    [root@vm-10-124-66-212 ~]# netstat -an|awk -F ' ' '{print $NF}'|sort |uniq -c |sort -rn|more 5552 TI ...

  5. 字幕字体滚动插件——scroxt.js

    README scroxt.js Overview scroxt.js是一个字体滚动的插件库,包括视频弹幕滚动,直播弹幕.直播弹幕强制模式.单行水平左右滚动.文本垂直滚动上下,用于简单快捷生成滚动字体 ...

  6. 提不起劲想赶紧完工 Scrum Meeting 博客汇总

    提不起劲想赶紧完工 Scrum Meeting 博客汇总 一.Alpha阶段 1,第一次Scrum Meeting 2,第二次Scrum Meeting 3,第三次Scrum Meeting 4,第四 ...

  7. 项目Beta冲刺团队随笔集

    博客集如下: Beta冲刺Day1:第一天冲刺记录 Beta冲刺Day2:第二天冲刺记录 Beta冲刺Day3:第三天冲刺记录 Beta冲刺Day4:第四天冲刺记录 Beta冲刺Day5:第五天冲刺记 ...

  8. 11_Java面向对象_第11天(接口、多态)_讲义

    今日内容介绍 1.接口 2.多态 01接口的概念 * A:接口的概念 接口是功能的集合,同样可看做是一种数据类型,是比抽象类更为抽象的"类". 接口只描述所应该具备的方法,并没有具 ...

  9. 05_Java基础语法_第5天(方法)_讲义

    今日内容介绍 1.方法基础知识 2.方法高级内容 3.方法案例 01方法的概述 * A: 为什么要有方法 * 提高代码的复用性 * B: 什么是方法 * 完成特定功能的代码块. 02方法的定义格式 * ...

  10. Gradle入门(1):安装

    在Ubuntu下,执行以下命令: sudo apt-get install gradle 安装完成后,执行命令: gradle -v 得到以下信息: Picked up _JAVA_OPTIONS:  ...