Matrix_tree Theorem 矩阵树定理学习笔记
Matrix_tree Theorem:
给定一个无向图, 定义矩阵A
A[i][j] = - (<i, j>之间的边数)
A[i][i] = 点i的度数
其生成树的个数等于 A的任意n - 1阶主子式的值。
关于定理的相关证明 可以看这篇文章, 讲得非常详细, 耐心看就能看懂:
关于求行列式, 可以用高斯消元。 如果是模域下求行列式, 可以用欧几里得算法。 具体实现看这篇文章
模域下求行列式 模板题:SPOJ DETER3
代码:
#include <cstdio>
#include <iostream>
#include <queue>
#include <algorithm>
#include <cstring>
#include <set>
#include <cmath>
using namespace std; #define N 220
#define M 400010
typedef long long ll; const int Mod=;
const double eps = 1e-; ll Solve(ll a[N][N], int n, ll mod)
{
ll res = ;
for (int i = ; i <= n; ++i)
{
for (int j = i; j <= n; ++j)
{
if (a[j][i] < )
{
res *= -;
for (int k = i; k <= n; ++k)
a[j][k] *= -;
}
}
int j;
for (j = i; j <= n && !a[j][i]; ++j);
if (j > n) return ; if (j != i)
{
res = -res;
for (int k = i; k <= n; ++k) swap(a[i][k], a[j][k]);
} for (j = i + ; j <= n; ++j)
{
while (a[j][i])
{
ll d = a[i][i] / a[j][i];
for (int k = i; k <= n; ++k) a[i][k] -= d * a[j][k] % mod, a[i][k] %= mod;
for (int k = i; k <= n; ++k) swap(a[i][k], a[j][k]);
res = -res;
}
}
res = res * a[i][i] % mod;
}
if (res < ) res += mod;
return res;
} int main()
{
//freopen("in.in","r",stdin);
//freopen("out.out","w",stdout); int n; ll mod;
while (scanf("%d %lld", &n, &mod) != EOF)
{
ll a[N][N];
for (int i = ; i <= n; ++i)
for (int j = ; j <= n; ++j)
scanf("%lld", &a[i][j]);
printf("%lld\n", Solve(a, n, mod));
} return ;
}
下面给出一些应用(练习题):
应用一:SPOJ HIGH
模板题: 给出一个无向图, 求生成树个数。
代码:
#include <cstdio>
#include <iostream>
#include <queue>
#include <algorithm>
#include <cstring>
#include <set>
#include <cmath>
using namespace std; #define N 13
#define M 400010
typedef long long ll; const int Mod=;
const double eps = 1e-; double Solve(int n, double a[N][N])
{
if (n == ) return ; /*for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
printf("%.0lf%c", a[i][j], j == n? '\n':' ');*/ double res = ;
for (int i = ; i <= n; ++i)
{
int j;
for (j = i; j <= n && fabs(a[j][i]) < eps; ++j);
if (j > n) return ;
if (j != i) for (int k = i; k <= n; ++k) swap(a[i][k], a[j][k]); for (int j = i + ; j <= n; ++j)
{
double f = a[j][i] / a[i][i];
for (int k = i; k <= n; ++k)
a[j][k] -= f * a[i][k];
}
res *= a[i][i];
} return res;
} int main()
{
//freopen("in.in","r",stdin);
//freopen("out.out","w",stdout); int T, n, m, x, y;
scanf("%d", &T);
while (T--)
{
double a[N][N] = {};
scanf("%d %d", &n, &m);
for (int i = ; i <= m; ++i)
{
scanf("%d %d", &x, &y);
a[x][y]--, a[y][x]--;
a[x][x]++, a[y][y]++;
}
printf("%.0lf\n", Solve(n - , a));
} return ;
}
应用二:BZOJ 4031
构图后 求生成树个数 mod 一个数。
#include <cstdio>
#include <iostream>
#include <queue>
#include <algorithm>
#include <cstring>
#include <set>
#include <cmath>
using namespace std; #define N 120
#define M 400010
typedef long long ll; const int Mod=;
const double eps = 1e-; ll Solve(ll a[N][N], int n, ll mod)
{
if (n == ) return ;
ll res = ;
for (int i = ; i <= n; ++i)
{
//for (int p = 1; p <= n; ++p)
// for (int q = 1; q <= n; ++q)
// printf("%lld%c", a[p][q], q == n? '\n':' '); for (int j = i; j <= n; ++j)
{
if (a[j][i] < )
{
res = -res;
for (int k = i; k <= n; ++k) a[j][k] *= -;
}
} int j;
for (j = i; j <= n && !a[j][i]; ++j);
if (j > n) return ; if (j != i)
{
res = -res;
for (int k = i; k <= n; ++k) swap(a[i][k], a[j][k]);
} for (j = i + ; j <= n; ++j)
{
while (a[j][i])
{
ll d = a[i][i] / a[j][i];
for (int k = i; k <= n; ++k) a[i][k] -= d * a[j][k] % mod, a[i][k] %= mod;
res = -res;
for (int k = i; k <= n; ++k) swap(a[i][k], a[j][k]);
}
}
res = res * a[i][i] % mod;
// printf("res = %lld\n", res);
}
if (res < ) res += mod;
// cout << "aa= "<<res <<endl;
return res;
} int main()
{
//freopen("in.in","r",stdin);
//freopen("out.out","w",stdout); int n, m; ll mod = ;
char mp[][]; int tot = ;
int id[][];
scanf("%d %d", &n, &m);
for (int i = ; i <= n; ++i)
{
for (int j = ; j <= m; ++j)
{
scanf(" %c", &mp[i][j]);
if (mp[i][j] == '.') id[i][j] = ++tot;
}
}
ll a[N][N] = {};
for (int i = ; i < n; ++i)
{
for (int j = ; j <= m; ++j)
{
if (mp[i][j] == '.' && mp[i + ][j] == '.')
{
int x = id[i][j], y = id[i + ][j];
a[x][y]--, a[y][x]--;
a[x][x]++, a[y][y]++;
}
}
}
for (int i = ; i <= n; ++i)
{
for (int j = ; j < m; ++j)
{
if (mp[i][j] == '.' && mp[i][j + ] == '.')
{
int x = id[i][j], y = id[i][j + ];
a[x][y]--, a[y][x]--;
a[x][x]++, a[y][y]++;
}
}
}
printf("%lld\n", Solve(a, tot - , mod));
return ;
}
应用三: BZOJ 2467
这题数据范围比较小,可以暴力建图 然后跑Matrix tree。
另外可以直接推公式:
一共有4n个点, 5n条边, 所以要删去n - 1条边, 然后可以发现 每个五边形外面的4条边最多只能删一条。
根据鸽笼原理合法的解 一定是 有一个五边形删去了里面的那条边 和外面的某条边, 其余的五边形删去了任意一条边。
所以答案就是$4*n*5^{n-1}$
Matrix tree 代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#include <queue>
#include <map>
using namespace std; #define X first
#define Y second
#define N 420
#define M 11 typedef long long ll;
const int Mod = ;
const int INF = << ; void Add(int x, int y, int &tot, int a[N][N])
{
a[x][tot + ] = a[tot + ][x] = -;
a[tot + ][tot + ] = a[tot + ][tot + ] = -;
a[tot + ][tot + ] = a[tot + ][tot + ] = -;
a[tot + ][y] = a[y][tot + ] = -;
a[tot + ][tot + ] = a[tot + ][tot + ] = a[tot + ][tot + ] = ;
a[x][x] = a[y][y] = ; tot += ;
a[x][y]--,a[y][x]--;
} int Solve(int n, int a[N][N], int mod)
{ if (n == ) return ;
int res = ;
for (int i = ; i <= n; ++i)
{
for (int j = i; j <= n; ++j)
{
if (a[j][i] < )
{
res = -res;
for (int k = i; k <= n; ++k) a[j][k] = -a[j][k];
}
}
//cout << i << endl;
//for (int p = 1; p <= n; ++p)
// for (int q = 1; q <= n; ++q)
// printf("%d%c", a[p][q], q == n? '\n':' ');
//printf("\n");
int j;
for (j = i; j <= n && !a[j][i]; ++j);
if (j > n) return ;
if (i != j)
{
res = -res;
for (int k = i; k <= n; ++k) swap(a[i][k], a[j][k]);
} for (j = i + ; j <= n; ++j)
{
while (a[j][i])
{
int d = a[i][i] / a[j][i];
for (int k = i; k <= n; ++k) a[i][k] -= d * a[j][k] % mod, a[i][k] %= mod;
res = -res;
for (int k = i; k <= n; ++k) swap(a[i][k], a[j][k]);
}
}
res = res * a[i][i] % mod;
}
if (res < ) res += mod;
return res;
} int main()
{
//freopen("in.in","r",stdin);
//freopen("out.out","w",stdout); int T; scanf("%d", &T);
while (T--)
{
int n, mod = , tot, a[N][N] = {}; scanf("%d", &n); tot = n;
for (int i = ; i < n; ++i) Add(i, i + , tot, a);
Add(n, , tot, a);
printf("%d\n", Solve(tot - , a, mod));
}
return ;
}
应用四:BZOJ 1016
题目大意:求最小生成树个数。
性质一:无向图所有MST中,相同权值的边数一样多。
证明看https://blog.sengxian.com/solutions/bzoj-1016
性质二:对于任意MST,加入所有权值<=w的边后, 形成的森林连通性相同 。
证明:
考虑Kruskal算法的过程,我们首先会尽可能多的加入权值最小的边。这个过程相当于拿出所有权值最小的边,然后任意求一颗生成树,因此我们可以知道,能加入的权值最小的边的数量是一定的,而且加入这些边之后 形成的森林连通性相同。
结合性质一,对于任意一棵MST,因为它包含的权值最小的边数和做Kruskal算法求出的MST包含的边数是一样的,这些边又不能形成环,因此这些边形成的森林和 做Kruskal时形成的森林连通性是一样的。 对于任意MST,加入所有权值最小的边后, 形成的森林连通性相同 。
然后我们考虑把已经形成的联通块缩点, 考虑所有权值第二小的边,重复上面的过程,可以证明对于任意MST,加入所有权值<=w的边后, 形成的森林连通性相同 。
所以我们的算法就可以模拟这个过程, 把边按权值从小到大排好序, 每次加入权值相同的所有边, 形成一些连通块, 然后对于每个连通块, 跑Matrix tree 求出形成这个连通块有多少种方案。 统计好之后每个连通块缩点, 进行下一种权值的边的加边操作。 代码实现起来还是挺多细节的。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#include <queue>
#include <map>
using namespace std; #define X first
#define Y second
#define N 120
#define M 1010 typedef long long ll;
const int Mod = ;
const int INF = << ; struct Edge
{
int x, y, z;
bool operator < (const Edge &t)const{return z < t.z;}
}e[M]; int Solve(int n, int a[N][N], int mod)
{
if (n == ) return ;
int res = ;
for (int i = ; i < n; ++i)
{
for (int j = i; j < n; ++j)
{
if (a[j][i] < )
{
res = -res;
for (int k = i; k < n; ++k) a[j][k] = -a[j][k];
}
}
int j;
for (j = i; j < n && !a[j][i]; ++j);
if (j == n) return ;
if (i != j)
{
res = -res;
for (int k = i; k < n; ++k) swap(a[i][k], a[j][k]);
} for (j = i + ; j < n; ++j)
{
while (a[j][i])
{
int d = a[i][i] / a[j][i];
for (int k = i; k < n; ++k) a[i][k] -= d * a[j][k] % mod, a[i][k] %= mod;
res = -res;
for (int k = i; k < n; ++k) swap(a[i][k], a[j][k]);
}
}
res = res * a[i][i] % mod;
}
if (res < ) res += mod;
return res;
} int father[N], id[N], pa[N], num[N];
vector<int> lis[N];
vector<pair<int, int> > ed[N]; int Find(int x)
{
if (father[x] == x) return x;
father[x] = Find(father[x]);
return father[x];
} void Merge(int x, int y)
{
x = Find(x), y = Find(y);
if (x != y) father[x] = y;
} int main()
{
//freopen("in.in","r",stdin);
//freopen("out.out","w",stdout); int n, m, mod = ;
scanf("%d %d", &n, &m);
for (int i = ; i <= m; ++i) scanf("%d %d %d", &e[i].x, &e[i].y, &e[i].z);
sort(e + , e + m + ); int res = , block = n;
for (int i = ; i <= n; ++i) id[i] = i;
for (int l = , r; l <= m;)
{
for (r = l; r < m && e[r + ].z == e[l].z; ++r);
for (int i = ; i <= block; ++i) father[i] = i;
for (r = l; r < m && e[r + ].z == e[l].z; ++r);
for (int i = l; i <= r; ++i) Merge(id[e[i].x], id[e[i].y]); int tot = ;
for (int i = ; i <= block; ++i) if (father[i] == i) pa[i] = ++tot;
for (int i = ; i <= block; ++i) pa[i] = pa[Find(i)];
for (int i = ; i <= block; ++i) lis[pa[i]].push_back(i), num[i] = lis[pa[i]].size() - ;
for (int i = l; i <= r; ++i)
{
int x = id[e[i].x], y = id[e[i].y];
if (x == y) continue;
ed[pa[x]].push_back(make_pair(num[x], num[y]));
}
for (int i = ; i <= tot; ++i)
{
int a[N][N] = {}, x, y;
for (int j = ; j < ed[i].size(); ++j)
{
x = ed[i][j].X, y = ed[i][j].Y;
a[x][x]++, a[y][y]++;
a[x][y]--, a[y][x]--;
}
res = res * Solve(lis[i].size() - , a, mod) % mod;
} for (int i = ; i <= n; ++i) id[i] = pa[id[i]];
for (int i = ; i <= tot; ++i) lis[i].clear(), ed[i].clear();
block = tot; l = r + ;
}
if (block > ) puts("");
else printf("%d\n", res);
return ;
}
应用五: BZOJ 3534 Matrix Tree Theorem 的扩展, 非常精彩的题。
题目大意:
给出一个无向图, 两点之间的连边会有一个概率, 求连成一颗树的概率。
这个题如果没有看过Matrix Tree Theorem定理的证明,只是记住结论 应该是做不出来的。。。
先来看一个简化版本:
给出一个无向图, 定义它的一棵生成树的权值为所有边权的乘积。 求所有生成树的权值和。 ( 原题还要考虑一些边不选的概率 这题只靠考虑选的边)
参考最前面给出的 证明Matrix Tree Theorem定理的文章。
原来是
A[i][j] = - (<i, j>之间的边数)
A[i][i] = 点i的度数
现在改成
A[i][j] = - (<i, j>之间的所有边权和)
A[i][i] = 和i相连的所有边的边权和
修改关联矩阵B的定义, 把1 改成 $e_j$的边权开根号后的值。
这里也做相应的修改, 把 -1 改成 -<i,j>之间的边权和, the degree of i 改成和i相连的所有边的边权和。
做了以上修改之后刚好就是所选的生成树的边权的乘积。
所以用修改后的A数组跑Matrix Tree就可以解决这个问题了。
当然原题 还要乘上 其他边不选的概率。 再对A数组做点小修改就好了。具体实现看代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#include <queue>
#include <map>
using namespace std; #define X first
#define Y second
#define N 100
#define M 11 typedef long long ll;
const int Mod=;
const int INF=<<; const double eps = 1e-; double Solve(double a[N][N], int n)
{
if (n == ) return ;
double res = ;
for (int i = ; i <= n; ++i)
{
int j = i;
for (int k = i + ; k <= n ; ++k) if (fabs(a[k][i]) > fabs(a[j][i])) j = k;
if (fabs(a[j][i]) < eps) return ;
if (i != j)
{
res = -res;
for (int k = i; k <= n; ++k) swap(a[i][k], a[j][k]);
} for (j = i + ; j <= n; ++j)
{
double f = a[j][i] / a[i][i];
for (int k = i; k <= n; ++k) a[j][k] -= f * a[i][k];
}
res *= a[i][i];
}
return res;
} int main()
{
//freopen("in.in","r",stdin);
//freopen("out.out","w",stdout); int n;
double a[N][N] = {}, res = ; scanf("%d", &n);
for (int i = ; i <= n; ++i)
{
for (int j = ; j <= n; ++j)
{
scanf("%lf", &a[i][j]);
if (i == j) continue;
if (i < j && fabs(a[i][j] - ) > eps) res *= - a[i][j];
if (fabs(a[i][j] - ) > eps) a[i][j] = -a[i][j] / ( - a[i][j]);
else a[i][j] = -a[i][j];
}
}
for (int i = ; i <= n; ++i)
for (int j = ; j <= n; ++j)
if (i != j) a[i][i] -= a[i][j];
printf("%.8lf\n", fabs(res * Solve(a, n - )));
return ;
}
Matrix_tree Theorem 矩阵树定理学习笔记的更多相关文章
- [HEOI2015]小Z的房间(矩阵树定理学习笔记)
题目描述 你突然有了一个大房子,房子里面有一些房间.事实上,你的房子可以看做是一个包含n*m个格子的格状矩形,每个格子是一个房间或者是一个柱子.在一开始的时候,相邻的格子之间都有墙隔着. 你想要打通一 ...
- 洛谷4455 [CQOI2018]社交网络 (有向图矩阵树定理)(学习笔记)
sro_ptx_orz qwq算是一个套路的记录 对于一个有向图来说 如果你要求一个外向生成树的话,那么如果存在一个\(u\rightarrow v\)的边 那么\(a[u][v]--,a[v][v] ...
- 矩阵树定理&BEST定理学习笔记
终于学到这个了,本来准备省选前学来着的? 前置知识:矩阵行列式 矩阵树定理 矩阵树定理说的大概就是这样一件事:对于一张无向图 \(G\),我们记 \(D\) 为其度数矩阵,满足 \(D_{i,i}=\ ...
- Note -「矩阵树定理」学习笔记
大概--会很简洁吧 qwq. 矩阵树定理 对于无自环无向图 \(G=(V,E)\),令其度数矩阵 \(D\),邻接矩阵 \(A\),令该图的 \(\text{Kirchhoff}\) 矩阵 \ ...
- [专题总结]矩阵树定理Matrix_Tree及题目&题解
专题做完了还是要说两句留下什么东西的. 矩阵树定理通俗点讲就是: 建立矩阵A[i][j]=edge(i,j),(i!=j).即矩阵这一项的系数是两点间直接相连的边数. 而A[i][i]=deg(i). ...
- [spoj104][Highways] (生成树计数+矩阵树定理+高斯消元)
In some countries building highways takes a lot of time... Maybe that's because there are many possi ...
- 【LOJ#6072】苹果树(矩阵树定理,折半搜索,容斥)
[LOJ#6072]苹果树(矩阵树定理,折半搜索,容斥) 题面 LOJ 题解 emmmm,这题似乎猫讲过一次... 显然先\(meet-in-the-middle\)搜索一下对于每个有用的苹果数量,满 ...
- 【算法】Matrix - Tree 矩阵树定理 & 题目总结
最近集中学习了一下矩阵树定理,自己其实还是没有太明白原理(证明)类的东西,但想在这里总结一下应用中的一些细节,矩阵树定理的一些引申等等. 首先,矩阵树定理用于求解一个图上的生成树个数.实现方式是:\( ...
- CF917D. Stranger Trees & TopCoder13369. TreeDistance(变元矩阵树定理+高斯消元)
题目链接 CF917D:https://codeforces.com/problemset/problem/917/D TopCoder13369:https://community.topcoder ...
随机推荐
- redis3.0.6安装配置
运行linux客户端 1 安装依赖 yum install gcc-c++ -y(安装gcc) 2 创建安装目录.编译.安装 mkdir -p /usr/local/redis(创建安 ...
- ORA-12519, TNS:no appropriate service handler found(数据库上当前的连接数目已经超过最大值)
报错: ORA-12519, TNS:no appropriate service handler foundThe Connection descriptor used by the client ...
- vb.net小试三层架构
在对三层架构有了初步了解后,用vb.net做了一个小的程序,真的很小,仅仅是为了体现一下三层之间机制.下面是我设计的操作界面: 还有程序集和类的分布情况, 接下来是数据的设计,数据库用到的是SQL S ...
- RxJava异步请求加载状态控制
在我看来,RxJava最大的特点就是异步,无论你是解析复杂的数据或是IO操作,我们都可以利用它内置的线程池进行线程间的调度,简单的使用 subscribeOn(Schedulers.io()).doO ...
- strtok()切割字符串函数
#include <iostream> #include <string.h> using namespace std; char *my_strtok(char *dist, ...
- 算法笔记_095:蓝桥杯练习 拿糖果(Java)
目录 1 问题描述 2 解决方案 1 问题描述 问题描述 妈妈给小B买了N块糖!但是她不允许小B直接吃掉. 假设当前有M块糖,小B每次可以拿P块糖,其中P是M的一个不大于根号下M的质因数.这时,妈 ...
- 一直加载“fonts.googleapis.com”的解决办法
原文:http://www.tuicool.com/articles/6bayeq 最近国内对google又开始了新一轮的屏蔽,很多wordpress用户发现一个现象,那就是网站前台和后台打开都非常慢 ...
- vi语法高亮,自动缩进(转)
编辑.vimrc 这样文件在其中加入如下两行就可以了,此法对#号提示符下的操作无效. let &termencoding=&encodingset fileencodings=utf- ...
- STL源码剖析(heap)
STL heap并不是容器,而是一系列的算法. 这些算法接受RandomAccessIterator[start, end),并将其表述成一棵完全二叉树. 关于heap算法可以参考之前算法导论的一篇博 ...
- 关于ThreadLocal变量的一个坑
每个线程都有一个ThreadLocalMap对象,ThreadLocalMap是Thread的一个内部类,可以把ThreadLocalMap理解成一个Map,这个Map里存放这一个Thread的所有线 ...