【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 ...
随机推荐
- DNS分离解析IPV6与IPV4用户
IPV6改造中经常会遇到,网站使用了CDN,但是CDN厂商还不支持IPV6的情况,而AAAA.A.CNAME记录互相冲突,想实现IPV6用户得到AAAA记录,IPV4用户得到CNAME记录的需求. 解 ...
- 【树莓派】crontab的两个问题
1,/var/log下面,没有cron.log日志 root@raspberrypi:/# nano /etc/rsyslog.conf …… …… ############### #### RULE ...
- 2017年第八届蓝桥杯【C++省赛B组】
1.标题: 购物单 小明刚刚找到工作,老板人很好,只是老板夫人很爱购物.老板忙的时候经常让小明帮忙到商场代为购物.小明很厌烦,但又不好推辞. 这不,XX大促销又来了!老板夫人开出了长长的购物单,都是有 ...
- 20135316王剑桥 linux第六周课实验笔记
6.存储器层次结构 6.1存储技术 1.如果你的程序需要的数据是存储在CPU寄存器中的,那么在执行期间,在零个周期内就能访问到它们.如果存储在高速缓冲中,需要1-10个周期.如果存储在主存中,需要50 ...
- 20162327WJH第一次实验——线性结构
20162327WJH第一次实验--线性结构 实 验 报 告 实 验 报 告 课程:程序设计与数据结构 班级: 1623 姓名: 王旌含 学号:20162327 成绩: 2分 指导教师:娄嘉鹏 王志强 ...
- centos6 安装图形化界面
1.首先进行光盘的挂载,注意光盘挂载时不会自动建立目录的,所以需要自己建立目录 mkdir /mnt/cdrom mount /dev/cdrom /mnt/cdrom #dev目录为设备目录 2.更 ...
- Oracle Form Builder
Oracle Form Builder 是Oracle的一个开发工具,可以针对Oracle公司的E-Business Suit的ERP系统开发的.对应的还有reports builder. Oracl ...
- alphe4
队名:massivehard 组员1:(组长:晓辉) 今天完成了哪些任务: 服务器基本架设完毕 明日计划: 服务器与客户端对接 用户手写记录功能的完善 还剩下哪些计划: 用户手写记录功能 服务器与客户 ...
- 团队作业4——第一次项目冲刺(Alpha版本)2017.11.16
1.当天站立式会议照片 本次会议在5号公寓3楼召开,本次会议内容:①:熟悉每个人想做的模块.②:根据老师的要求将项目划分成一系列小任务.③:在上次会议内容完成的基础上增加新的任务. 2.每个人的工作 ...
- jQuery ajax - get() 方法
AJAX = Asynchronous javaScript and XML. AJAX 是一种创建快速动态网页的技术. AJAX 通过在后台与服务器交换少量数据的方式,允许网页进行异步更新.这意味 ...