【Learning】矩阵树定理 Matrix-Tree
矩阵树定理 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)\),则
\]
复杂度为\(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的更多相关文章
- 矩阵树定理(Matrix Tree)学习笔记
如果不谈证明,稍微有点线代基础的人都可以在两分钟内学完所有相关内容.. 行列式随便找本线代书看一下基本性质就好了. 学习资源: https://www.cnblogs.com/candy99/p/64 ...
- @总结 - 7@ 生成树计数 —— matrix - tree 定理(矩阵树定理)与 prüfer 序列
目录 @0 - 参考资料@ @0.5 - 你所需要了解的线性代数知识@ @1 - 矩阵树定理主体@ @证明 part - 1@ @证明 part - 2@ @证明 part - 3@ @证明 part ...
- 【算法】Matrix - Tree 矩阵树定理 & 题目总结
最近集中学习了一下矩阵树定理,自己其实还是没有太明白原理(证明)类的东西,但想在这里总结一下应用中的一些细节,矩阵树定理的一些引申等等. 首先,矩阵树定理用于求解一个图上的生成树个数.实现方式是:\( ...
- 2018.09.16 spoj104Highways (矩阵树定理)
传送门 第一次写矩阵树定理. 就是度数矩阵减去邻接矩阵之后得到的基尔霍夫矩阵的余子式的行列式值. 这个可以用高斯消元O(n3)" role="presentation" ...
- luoguP3317 [SDOI2014]重建 变元矩阵树定理 + 概率
首先,我们需要求的是 $$\sum\limits_{Tree} \prod\limits_{E \in Tree} E(u, v) \prod\limits_{E \notin Tree} (1 - ...
- BZOJ3534 [Sdoi2014]重建 【矩阵树定理】
题目 T国有N个城市,用若干双向道路连接.一对城市之间至多存在一条道路. 在一次洪水之后,一些道路受损无法通行.虽然已经有人开始调查道路的损毁情况,但直到现在几乎没有消息传回. 辛运的是,此前T国政府 ...
- [专题总结]矩阵树定理Matrix_Tree及题目&题解
专题做完了还是要说两句留下什么东西的. 矩阵树定理通俗点讲就是: 建立矩阵A[i][j]=edge(i,j),(i!=j).即矩阵这一项的系数是两点间直接相连的边数. 而A[i][i]=deg(i). ...
- Wannafly挑战赛23F-计数【原根,矩阵树定理,拉格朗日插值】
正题 题目链接:https://ac.nowcoder.com/acm/contest/161/F 题目大意 给出\(n\)个点的一张图,求它的所有生成树中权值和为\(k\)的倍数的个数.输出答案对\ ...
- [spoj104][Highways] (生成树计数+矩阵树定理+高斯消元)
In some countries building highways takes a lot of time... Maybe that's because there are many possi ...
随机推荐
- 《spark机器学习 第二版》 蔡立宇 分享 pdf下载
链接:https://pan.baidu.com/s/15Y14eAnfj8zf5mXdixbVeQ 提取码:rkdt
- [Processing]点到线段的最小距离
PVector p1,p2,n; float d = 0; void setup() { size(600,600); p1 = new PVector(150,30);//线段第一个端点 p2 = ...
- 有序链表转换二叉搜索树(LeetCode)
将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树. 本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1. 示例: 给定有序数组: [-10,-3,0, ...
- gcc 与 g++的区分较
一:gcc与g++比较 误区一:gcc只能编译c代码,g++只能编译c++代码两者都可以,但是请注意:1.后缀为.c的,gcc把它当作是C程序,而g++当作是c++程序:后缀为.cpp的,两者都会认为 ...
- ceilometer 源码分析(polling)(O版)
一.简单介绍ceilometer 这里长话短说, ceilometer是用来采集openstack下面各种资源的在某一时刻的资源值,比如云硬盘的大小等.下面是官网现在的架构图 这里除了ceilomet ...
- 高可用OpenStack(Queen版)集群-10.Nova计算节点
参考文档: Install-guide:https://docs.openstack.org/install-guide/ OpenStack High Availability Guide:http ...
- [zabbix] zabbix从内部检测web页面
环境说明: 两台机器各运行一个tomcat实例,通过阿里云slb到后端,假设后端服务挂了一个,从外部访问整个服务还是可用的,所以需要从内部检测web页面. zabbix自带的web场景都是从外部检测w ...
- leetcode 184 部门工资最高的员工
题目描述:Employee 表包含所有员工信息,每个员工有其对应的 Id, salary 和 department Id. Department 表包含公司所有部门的信息. 编写一个 SQL 查询,找 ...
- 使用Axure RP设计Android界面原型
转至@徐州瑞步科技(http://www.cnblogs.com/brooks-dotnet/archive/2013/06/05/3119923.html) 资源地址:http://pan.baid ...
- mac上查找nginx安装位置
在终端输入: nginx -V 查看nginx版本及安装的本地位置 ngxin -v 查看nginx版本(此方法依然可以检测是否安装某一软件,如git,hg等)