Matrix_tree Theorem 学习笔记
Posted lzw4896s
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Matrix_tree Theorem 学习笔记相关的知识,希望对你有一定的参考价值。
Matrix_tree Theorem:
给定一个无向图, 定义矩阵A
A[i][j] = - (<i, j>之间的边数)
A[i][i] = 点i的度数
其生成树的个数等于 A的任意n - 1阶主子式的值。
关于定理的相关证明 可以看这篇文章, 讲得非常详细, 耐心看就能看懂:
关于求行列式, 可以用高斯消元。 如果是模域下求行列式, 可以用欧几里得算法。 具体实现看这篇文章
模域下求行列式 模板题:SPOJ DETER3
代码:
1 #include <cstdio>
2 #include <iostream>
3 #include <queue>
4 #include <algorithm>
5 #include <cstring>
6 #include <set>
7 #include <cmath>
8 using namespace std;
9
10 #define N 220
11 #define M 400010
12 typedef long long ll;
13
14 const int Mod=1000000007;
15 const double eps = 1e-9;
16
17 ll Solve(ll a[N][N], int n, ll mod)
18 {
19 ll res = 1;
20 for (int i = 1; i <= n; ++i)
21 {
22 for (int j = i; j <= n; ++j)
23 {
24 if (a[j][i] < 0)
25 {
26 res *= -1;
27 for (int k = i; k <= n; ++k)
28 a[j][k] *= -1;
29 }
30 }
31 int j;
32 for (j = i; j <= n && !a[j][i]; ++j);
33 if (j > n) return 0;
34
35 if (j != i)
36 {
37 res = -res;
38 for (int k = i; k <= n; ++k) swap(a[i][k], a[j][k]);
39 }
40
41 for (j = i + 1; j <= n; ++j)
42 {
43 while (a[j][i])
44 {
45 ll d = a[i][i] / a[j][i];
46 for (int k = i; k <= n; ++k) a[i][k] -= d * a[j][k] % mod, a[i][k] %= mod;
47 for (int k = i; k <= n; ++k) swap(a[i][k], a[j][k]);
48 res = -res;
49 }
50 }
51 res = res * a[i][i] % mod;
52 }
53 if (res < 0) res += mod;
54 return res;
55 }
56
57 int main()
58 {
59 //freopen("in.in","r",stdin);
60 //freopen("out.out","w",stdout);
61
62 int n; ll mod;
63 while (scanf("%d %lld", &n, &mod) != EOF)
64 {
65 ll a[N][N];
66 for (int i = 1; i <= n; ++i)
67 for (int j = 1; j <= n; ++j)
68 scanf("%lld", &a[i][j]);
69 printf("%lld\\n", Solve(a, n, mod));
70 }
71
72 return 0;
73 }
下面给出一些应用(练习题):
应用一:SPOJ HIGH
模板题: 给出一个无向图, 求生成树个数。
代码:
1 #include <cstdio>
2 #include <iostream>
3 #include <queue>
4 #include <algorithm>
5 #include <cstring>
6 #include <set>
7 #include <cmath>
8 using namespace std;
9
10 #define N 13
11 #define M 400010
12 typedef long long ll;
13
14 const int Mod=1000000007;
15 const double eps = 1e-9;
16
17 double Solve(int n, double a[N][N])
18 {
19 if (n == 0) return 1;
20
21 /*for (int i = 1; i <= n; ++i)
22 for (int j = 1; j <= n; ++j)
23 printf("%.0lf%c", a[i][j], j == n? \'\\n\':\' \');*/
24
25 double res = 1;
26 for (int i = 1; i <= n; ++i)
27 {
28 int j;
29 for (j = i; j <= n && fabs(a[j][i]) < eps; ++j);
30 if (j > n) return 0;
31 if (j != i) for (int k = i; k <= n; ++k) swap(a[i][k], a[j][k]);
32
33 for (int j = i + 1; j <= n; ++j)
34 {
35 double f = a[j][i] / a[i][i];
36 for (int k = i; k <= n; ++k)
37 a[j][k] -= f * a[i][k];
38 }
39 res *= a[i][i];
40 }
41
42 return res;
43 }
44
45 int main()
46 {
47 //freopen("in.in","r",stdin);
48 //freopen("out.out","w",stdout);
49
50 int T, n, m, x, y;
51 scanf("%d", &T);
52 while (T--)
53 {
54 double a[N][N] = {0};
55 scanf("%d %d", &n, &m);
56 for (int i = 1; i <= m; ++i)
57 {
58 scanf("%d %d", &x, &y);
59 a[x][y]--, a[y][x]--;
60 a[x][x]++, a[y][y]++;
61 }
62 printf("%.0lf\\n", Solve(n - 1, a));
63 }
64
65 return 0;
66 }
应用二:BZOJ 4031
构图后 求生成树个数 mod 一个数。
1 #include <cstdio>
2 #include <iostream>
3 #include <queue>
4 #include <algorithm>
5 #include <cstring>
6 #include <set>
7 #include <cmath>
8 using namespace std;
9
10 #define N 120
11 #define M 400010
12 typedef long long ll;
13
14 const int Mod=1000000007;
15 const double eps = 1e-9;
16
17 ll Solve(ll a[N][N], int n, ll mod)
18 {
19 if (n == 0) return 1;
20 ll res = 1;
21 for (int i = 1; i <= n; ++i)
22 {
23 //for (int p = 1; p <= n; ++p)
24 // for (int q = 1; q <= n; ++q)
25 // printf("%lld%c", a[p][q], q == n? \'\\n\':\' \');
26
27 for (int j = i; j <= n; ++j)
28 {
29 if (a[j][i] < 0)
30 {
31 res = -res;
32 for (int k = i; k <= n; ++k) a[j][k] *= -1;
33 }
34 }
35
36 int j;
37 for (j = i; j <= n && !a[j][i]; ++j);
38 if (j > n) return 0;
39
40 if (j != i)
41 {
42 res = -res;
43 for (int k = i; k <= n; ++k) swap(a[i][k], a[j][k]);
44 }
45
46
47 for (j = i + 1; j <= n; ++j)
48 {
49 while (a[j][i])
50 {
51 ll d = a[i][i] / a[j][i];
52 for (int k = i; k <= n; ++k) a[i][k] -= d * a[j][k] % mod, a[i][k] %= mod;
53 res = -res;
54 for (int k = i; k <= n; ++k) swap(a[i][k], a[j][k]);
55 }
56 }
57 res = res * a[i][i] % mod;
58 // printf("res = %lld\\n", res);
59 }
60 if (res < 0) res += mod;
61 // cout << "aa= "<<res <<endl;
62 return res;
63 }
64
65 int main()
66 {
67 //freopen("in.in","r",stdin);
68 //freopen("out.out","w",stdout);
69
70 int n, m; ll mod = 1000000000;
71 char mp[11][11]; int tot = 0;
72 int id[11][11];
73 scanf("%d %d", &n, &m);
74 for (int i = 1; i <= n; ++i)
75 {
76 for (int j = 1; j <= m; ++j)
77 {
78 scanf(" %c", &mp[i][j]);
79 if (mp[i][j] == \'.\') id[i][j] = ++tot;
80 }
81 }
82 ll a[N][N] = {0};
83 for (int i = 1; i < n; ++i)
84 {
85 for (int j = 1; j <= m; ++j)
86 {
87 if (mp[i][j] == \'.\' && mp[i + 1][j] == \'.\')
88 {
89 int x = id[i][j], y = id[i + 1][j];
90 a[x][y]--, a[y][x]--;
91 a[x][x]++, a[y][y]++;
92 }
93 }
94 }
95 for (int i = 1; i <= n; ++i)
96 {
97 for (int j = 1; j < m; ++j)
98 {
99 if (mp[i][j] == \'.\' && mp[i][j + 1] == \'.\')
100 {
101 int x = id[i][j], y = id[i][j + 1];
102 a[x][y]--, a[y][x]--;
103 a[x][x]++, a[y][y]++;
104 }
105 }
106 }
107 printf("%lld\\n", Solve(a, tot - 1, mod));
108 return 0;
109 }
应用三: BZOJ 2467
这题数据范围比较小,可以暴力建图 然后跑Matrix tree。
另外可以直接推公式:
一共有4n个点, 5n条边, 所以要删去n - 1条边, 然后可以发现 每个五边形外面的4条边最多只能删一条。
根据鸽笼原理合法的解 一定是 有一个五边形删去了里面的那条边 和外面的某条边, 其余的五边形删去了任意一条边。
所以答案就是$4*n*5^{n-1}$
Matrix tree 代码:
1 #include <iostream>
2 #include <cstdio>
3 #include <cstring>
4 #include <algorithm>
5 #include <vector>
6 #include <cmath>
7 #include <queue>
8 #include <map>
9 using namespace std;
10
11 #define X first
12 #define Y second
13 #define N 420
14 #define M 11
15
16 typedef long long ll;
17 const int Mod = 1000000007;
18 const int INF = 1 << 30;
19
20 void Add(int x, int y, int &tot, int a[N][N])
21 {
22 a[x][tot + 1] = a[tot + 1][x] = -1;
23 a[tot + 1][tot + 2] = a[tot + 2][tot + 1] = -1;
24 a[tot + 2][tot + 3] = a[tot + 3][tot + 2] = -1;
25 a[tot + 3][y] = a[y][tot + 3] = -1;
26 a[tot + 1][tot + 1] = a[tot + 2][tot + 2] = a[tot + 3][tot + 3] = 2;
27 a[x][x] = a[y][y] = 4; tot += 3;
28 a[x][y]--,a[y][x]--;
29 }
30
31 int Solve(int n, int a[N][N], int mod)
32 {
33
34 if (n == 0) return 1;
35 int res = 1;
36 for (int i = 1; i <= n; ++i)
37 {
38 for (int j = i; j <= n; ++j)
39 {
40 if (a[j][i] < 0)
41 {
42 res = -res;
43 for (int k = i; k <= n; ++k) a[j][k] = -a[j][k];
44 }
45 }
46 //cout << i << endl;
47 //for (int p = 1; p <= n; ++p)
48 // for (int q = 1; q <= n; ++q)
49 // printf("%d%c", a[p][q], q == n? \'\\n\':\' \');
50 //printf("\\n");
51 int j;
52 for (j = i; j <= n && !a[j][i]; ++j);
53 if (j > n) return 0;
54 if (i != j)
55 {
56 res = -res;
57 for (int k = i; k <= n; ++k) swap(a[i][k], a[j][k]);
58 }
59
60 for (j = i + 1; j <= n; ++j)
61 {
62 while (a[j][i])
63 {
64 int d = a[i][i] / a[j][i];
65 for (int k = i; k <= n; ++k) a[i][k] -= d * a[j][k] % mod, a[i][k] %= mod;
66 res = -res;
67 for (int k = i; k <= n; ++k) swap(a[i][k], a[j][k]);
68 }
69 }
70 res = res * a[i][i] % mod;
71 }
72 if (res < 0) res += mod;
73 return res;
74 }
75
76 int main()
77 {
78 //freopen("in.in","r",stdin);
79 //freopen("out.out","w",stdout);
80
81 int T; scanf("%d", &T);
82 while (T--)
83 {
84 int n, mod = 2007, tot, a[N][N] = {0}; scanf("%d", &n); tot = n;
85 for (int i = 1; i < n; ++i) Add(i, i + 1, tot, a);
86 Add(n, 1, tot, a);
87 printf("%d\\n", Solve(tot - 1, a, mod));
88 }
89 return 0;
90 }
应用四:BZOJ 1016
题目大意:求最小生成树个数。
性质一:无向图所有MST中,相同权值的边数一样多。
证明看https://blog.sengxian.com/solutions/bzoj-1016
性质二:对于任意MST,加入所有权值<=w的边后, 形成的森林连通性相同 。
证明:
考虑Kruskal算法的过程,我们首先会尽可能多的加入权值最小的边。这个过程相当于拿出所有权值最小的边,然后任意求一颗生成树,因此我们可以知道,能加入的权值最小的边的数量是一定的,而且加入这些边之后 形成的森林连通性相同。
结合性质一,对于任意一棵MST,因为它包含的权值最小的边数和做Kruskal算法求出的MST包含的边数是一样的,这些边又不能形成环,因此这些边形成的森林和 做Kruskal时形成的森林连通性是一样的。 对于任意MST,加入所有权值最小的边后, 形成的森林连通性相同 。
然后我们考虑把已经形成的联通块缩点, 考虑所有权值第二小的边,重复上面的过程,可以证明对于任意MST,加入所有权值<=w的边后, 形成的森林连通性相同 。
所以我们的算法就可以模拟这个过程, 把边按权值从小到大排好序, 每次加入权值相同的所有边, 形成一些连通块, 然后对于每个连通块, 跑Matrix tree 求出形成这个连通块有多少种方案。 以上是关于Matrix_tree Theorem 学习笔记的主要内容,如果未能解决你的问题,请参考以下文章