最近集中学习了一下矩阵树定理,自己其实还是没有太明白原理(证明)类的东西,但想在这里总结一下应用中的一些细节,矩阵树定理的一些引申等等。

  首先,矩阵树定理用于求解一个图上的生成树个数。实现方式是:\(A\)为邻接矩阵,\(D\)为度数矩阵,则基尔霍夫(Kirchhoff)矩阵即为:\(K = D - A\)。具体实现中,记 \(a\) 为Kirchhoff矩阵,则若存在 \(E(u, v)\) ,则\(a[u][u] ++, a[v][v] ++, a[u][v] --, a[v][u] --\) 。即\(a[i][i]\) 为 \(i\) 点的度数,\(a[i][j]\) 为 \(i, j\)之间边的条数的相反数。

  这样构成的矩阵的行列式的值,就为生成树的个数。而求解行列式的快速方法为使用高斯消元进行消元消处上三角矩阵,则有对角线上的值的乘积 = 行列式的值。一般而言求解生成树个数的题目数量会非常庞大,需要取模处理。取模处理中,不能出现小数,于是使用辗转相除法:(其中因为消的是行列式,所以与消方程有所不同。交换两行行列式的值变号,且消元只能将一行的数 * k 之后加到别的行上。)

int Gauss()
{
int ans = ;
for(int i = ; i < tot; i ++)
{
for(int j = i + ; j < tot; j ++)
while(f[j][i])
{
int t = f[i][i] / f[j][i];
for(int k = i; k < tot; k ++)
f[i][k] = (f[i][k] - t * f[j][k] + mod) % mod;
swap(f[i], f[j]);
ans = - ans;
}
ans = (ans * f[i][i]) % mod;
}
return (ans + mod) % mod;
}

  变元矩阵树定理:求所有生成树的总边积的和。和矩阵树的求法相同,不过行列式中 \(a[i][i]\) 记录的是总的边权和,\(a[i][j]\) 记录 \(i, j\) 之间边权的相反数。

  以下为几道题目:

    1.HEOI2015 小Z的房间     2.SHOI2016 黑暗前的幻想乡

    3.SDOI2014 重建         4.JSOI2008 最小生成树计数

  1.HEOI2015 小Z的房间(妥妥的模板题一个)

#include <bits/stdc++.h>
using namespace std;
#define maxn 90
#define int long long
#define mod 1000000000
int n, m, f[maxn][maxn];
int tot, Map[maxn][maxn]; int read()
{
int x = , k = ;
char c;
c = getchar();
while(c < '' || c > '') { if(c == '-') k = -; c = getchar(); }
while(c >= '' && c <= '') x = x * + c - '', c = getchar();
return x * k;
} void add(int x, int y)
{
if(x > y) return;
f[x][x] ++, f[y][y] ++;
f[x][y] --, f[y][x] --;
} int Gauss()
{
int ans = ;
for(int i = ; i < tot; i ++)
{
for(int j = i + ; j < tot; j ++)
while(f[j][i])
{
int t = f[i][i] / f[j][i];
for(int k = i; k < tot; k ++)
f[i][k] = (f[i][k] - t * f[j][k] + mod) % mod;
swap(f[i], f[j]);
ans = - ans;
}
ans = (ans * f[i][i]) % mod;
}
return (ans + mod) % mod;
} signed main()
{
n = read(), m = read();
for(int i = ; i <= n; i ++)
{
char c;
for(int j = ; j <= m; j ++)
{
cin >> c;
if(c == '.') Map[i][j] = ++ tot;
}
}
for(int i = ; i <= n; i ++)
for(int j = ; j <= m; j ++)
{
int tem, u;
if(!(u = Map[i][j])) continue;
if(tem = Map[i - ][j]) add(u, tem);
if(tem = Map[i + ][j]) add(u, tem);
if(tem = Map[i][j - ]) add(u, tem);
if(tem = Map[i][j + ]) add(u, tem);
}
printf("%lld\n", Gauss());
return ;
}

  2.SHOI2016黑暗前的幻想乡

  容斥+矩阵树定理。与模板的不同之处在于每一家公司都要参与修建,则合法方案数 = 总的方案数 - 有一个公司未修建的方案数 + 有两个公司未修建的方案数……暴力重构矩阵求解即可。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int mod = ;
int n;
ll g[][];
vector<pair<int , int > > q[]; int read()
{
int x = , k = ;
char c;
c = getchar();
while(c < '' || c > '') { if(c == '-') k = -; c = getchar(); }
while(c >= '' && c <= '') x = x * + c - '', c = getchar();
return x * k;
} int Gauss()
{
ll ans = ;
for(int i = ; i < n; i ++)
{
for(int j = i + ; j < n; j ++)
while(g[j][i])
{
ll t = g[i][i] / g[j][i];
for(int k = i; k < n; k ++)
g[i][k] = (g[i][k] - g[j][k] * t) % mod;
swap(g[i], g[j]);
ans = -ans;
}
ans = (ans * g[i][i]) % mod;
if(!ans) return ;
}
return (ans + mod) % mod;
} int main()
{
n = read();
for(int i = ; i < n; i ++)
{
int m = read();
for(int j = ; j <= m; j ++)
{
int x = read(), y = read();
q[i].push_back(make_pair(x, y));
}
}
int ans = , CNST = << (n - );
for(int i = ; i < CNST; i ++)
{
int cnt = ; memset(g, , sizeof(g));
for(int j = ; j < n; j ++)
if(i & ( << (j - )))
{
for(int k = ; k < q[j].size(); k ++)
{
int x = q[j][k].first, y = q[j][k].second;
g[x][x] ++, g[y][y] ++;
g[x][y] --, g[y][x] --;
}
cnt ++;
}
if((n - cnt) & ) ans = (ans + Gauss()) % mod;
else ans = (ans - Gauss() + mod) % mod;
}
printf("%d\n", ans);
return ;
}

  3.SDOI2014重建

  化式子 + 变元矩阵树定理。将概率的式子写出来变形即可得到矩阵树定理求 \(\prod \frac{p(u, v)}{1 - p(u, v)}\)

#include <bits/stdc++.h>
using namespace std;
#define maxn 100
#define db double
#define eps 0.000001
int n;
db ans = 1.0, a[maxn][maxn]; db Gauss(int n)
{
db ans = 1.0;
for(int i = ; i <= n; i ++)
{
for(int j = i + ; j <= n; j ++)
{
int t = i;
if(fabs(a[j][i]) > fabs(a[t][i])) t = j;
if(t != i) swap(a[t], a[i]), ans = -ans;
}
for(int j = i + ; j <= n; j ++)
{
db t = a[j][i] / a[i][i];
for(int k = i; k <= n; k ++)
a[j][k] -= t * a[i][k];
}
ans *= a[i][i];
}
return fabs(ans);
} int main()
{
scanf("%d", &n);
for(int i = ; i <= n; i ++)
for(int j = ; j <= n; j ++)
scanf("%lf", &a[i][j]);
for(int i = ; i <= n; i ++)
for(int j = ; j <= n; j ++)
{
db t = fabs(1.0 - a[i][j]) < eps ? eps : (1.0 - a[i][j]);
if(i < j) ans *= t;
a[i][j] = a[i][j] / t;
}
for(int i = ; i <= n; i ++)
for(int j = ; j <= n; j ++)
if(i != j) { a[i][i] += a[i][j], a[i][j] = -a[i][j]; }
printf("%.10lf\n", Gauss(n - ) * ans);
return ;
}

  4.JSOI2008最小生成树计数

  这题虽然最早年,然而也最强啊……个人认为这位博主解释得很好了 Z-Y-Y-S的博客

  两个性质 mark 一下:

  

  

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define maxn 200
#define mod 31011
int n, m, ans = , tmp[maxn];
int sum, fa[maxn], set[maxn];
int a[maxn][maxn]; struct edge
{
int u, v, w;
}E[maxn * ], e[maxn * ]; int read()
{
int x = , k = ;
char c;
c = getchar();
while(c < '' || c > '') { if(c == '-') k = -; c = getchar(); }
while(c >= '' && c <= '') x = x * + c - '', c = getchar();
return x * k;
} bool cmp(edge a, edge b) { return a.w < b.w; } int find(int x) { return set[x] == x ? x : set[x] = find(set[x]); }
int find2(int x) { return fa[x] == x ? x : fa[x] = find2(fa[x]); } int Gauss(int n)
{
int ans = ;
for(int i = ; i <= n; i ++)
for(int j = ; j <= n; j ++)
a[i][j] = (a[i][j] + mod) % mod;
for(int i = ; i <= n; i ++)
{
for(int j = i + ; j <= n; j ++)
while(a[j][i])
{
int t = a[i][i] / a[j][i];
for(int k = i; k <= n; k ++)
a[i][k] = (a[i][k] - 1ll * t * a[j][k] % mod + mod) % mod;
swap(a[i], a[j]); ans = - ans;
}
ans = 1ll * ans * a[i][i] % mod;
}
return (ans + mod) % mod;
} void Cal(int S, int T)
{
int cnt = ;
for(int i = S; i <= T; i ++)
{
e[i] = E[i];
int p = find(e[i].u), q = find(e[i].v);
e[i].u = p, e[i].v = q;
if(p == q) continue;
tmp[++ cnt] = p, tmp[++ cnt] = q;
}
sort(tmp + , tmp + + cnt);
cnt = unique(tmp + , tmp + cnt + ) - tmp - ;
memset(a, , sizeof(a));
for(int i = ; i <= cnt; i ++) fa[i] = i;
for(int i = S; i <= T; i ++)
{
if(e[i].u == e[i].v) continue;
int p = find(e[i].u), q = find(e[i].v);
if(p != q) -- sum, set[p] = q;
int u = lower_bound(tmp + , tmp + cnt + , e[i].u) - tmp;
int v = lower_bound(tmp + , tmp + cnt + , e[i].v) - tmp;
a[u][u] ++, a[v][v] ++;
a[u][v] --, a[v][u] --;
p = find2(u), q = find2(v);
if(p != q) fa[p] = q;
}
for(int i = ; i <= cnt; i ++)
if(find2(i) != find2(i - ))
{
int p = find2(i), q = find2(i - );
a[p][p] ++, a[q][q] ++;
a[p][q] --, a[q][p] --;
fa[p] = q;
}
ans = 1ll * ans * Gauss(cnt - ) % mod;
} int main()
{
n = read(), m = read();
for(int i = ; i <= m; i ++)
E[i].u = read(), E[i].v = read(), E[i].w = read();
sort(E + , E + + m, cmp);
for(int i = ; i <= n; i ++) set[i] = i;
sum = n;
for(int i = , j; i <= m; i = j)
{
for(j = i; j <= m; j ++)
if(E[j].w != E[i].w) break;
if(j - i > ) Cal(i, j - );
else
{
int p = find(E[i].u), q = find(E[i].v);
if(p != q) set[p] = q;
sum --;
}
}
if(sum > ) printf("");
else printf("%d\n", ans);
return ;
}

【算法】Matrix - Tree 矩阵树定理 & 题目总结的更多相关文章

  1. bzoj 4031: 小Z的房间 矩阵树定理

    bzoj 4031: 小Z的房间 矩阵树定理 题目: 你突然有了一个大房子,房子里面有一些房间.事实上,你的房子可以看做是一个包含n*m个格子的格状矩形,每个格子是一个房间或者是一个柱子.在一开始的时 ...

  2. 【bzoj4031】[HEOI2015]小Z的房间 矩阵树定理

    题目描述 你突然有了一个大房子,房子里面有一些房间.事实上,你的房子可以看做是一个包含n*m个格子的格状矩形,每个格子是一个房间或者是一个柱子.在一开始的时候,相邻的格子之间都有墙隔着. 你想要打通一 ...

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

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

  4. 算法复习——矩阵树定理(spoj104)

    题目: In some countries building highways takes a lot of time... Maybe that's because there are many p ...

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

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

  6. 【Learning】矩阵树定理 Matrix-Tree

    矩阵树定理 Matrix Tree ​ 矩阵树定理主要用于图的生成树计数. 看到给出图求生成树的这类问题就大概要往这方面想了. 算法会根据图构造出一个特殊的基尔霍夫矩阵\(A\),接着根据矩阵树定理, ...

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

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

  8. [洛谷U22156]未曾届到游览(矩阵树定理)

    题目背景 又到了某任*堂开关中学一年一度的自主招生考试的时间了,在考试完后许多家长决定带着自己的孩子参观一下这所距千年名校还有890周年的百年学校: 题目描述 这所学校的布局非常奇怪,是一个由N 个点 ...

  9. bzoj1016 [JSOI2008]最小生成树计数——Kruskal+矩阵树定理

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1016 从 Kruskal 算法的过程来考虑产生多种方案的原因,就是边权相同的边有一样的功能, ...

随机推荐

  1. docker实战

    docker基础入门 docker网络

  2. Scrum 团队成立 -- 软件工程

      团队项目选题  : 金融工具:复利计算与投资记录项目继续升级,开发定位明确.功能专注的工具类软件 团队队员 : 蔡舜 , 林宇粲 , 王昕明 , 卢晓洵 团队目标 : 不断完善 团队口号 : 永不 ...

  3. SSH登录到远程linux机器并执行命令

    一. 1.JSch是Java Secure Channel的缩写.JSch是一个SSH2的纯Java实现.它允许你连接到一个SSH服务器,并且可以使用端口转发,X11转发,文件传输等,当然你也可以集成 ...

  4. Laravel5.5 使用队列 Queue

    使用队列# 上一章节中我们开发了自动生成 Slug 功能,但是因为我们的需要实时请求百度翻译接口,这将会是一个系统性能隐患. 一般情况下,网络请求会存在各种不确定性,如果请求 API 出现超时情况,或 ...

  5. Git 初始状操作指引

    You have an empty repository To get started you will need to run these commands in your terminal. Ne ...

  6. Storm 系列(三)Storm 集群部署和配置

    Storm 系列(二)Storm 集群部署和配置 本章中主要介绍了 Storm 的部署过程以及相关的配置信息.通过本章内容,帮助读者从零开始搭建一个 Storm 集群. 一.Storm 的依赖组件 1 ...

  7. 在cygwin中执行bat文件

    在cygwin中执行bat文件 #!/bin/bash dir_gen_image_bat=C:/MinGW/msys/1.0/home/Administrator/feitian_audio/FOT ...

  8. fastjson解析List对象

    List<String[]> body = JSON.parseObject(msg.getBody().toString(), new TypeToken<List<Stri ...

  9. 2018.10.14 loj#516. DP 一般看规律(启发式合并)

    传送门 注意到一种颜色改了之后就不能改回去了. 因此可以启发式合并. 每次把小的合并给大的. 这样每个数最多被合并logloglog次. 如果维护一棵比较下标的平衡树的话,对于答案有贡献的就是每个数与 ...

  10. UVa 12230 && HDU 3232 Crossing Rivers (数学期望水题)

    题意:你要从A到B去上班,然而这中间有n条河,距离为d.给定这n条河离A的距离p,长度L,和船的移动速度v,求从A到B的时间的数学期望. 并且假设出门前每条船的位置是随机的,如果不是在端点,方向也是不 ...