BZOJ4031 [HEOI2015] 小Z的房间
@(BZOJ)[行列式, Matrix-Tree定理, 高斯消元]
Description
你突然有了一个大房子,房子里面有一些房间。事实上,你的房子可以看做是一个包含\(n * m\)个格子的格状矩形,每个格子是一个房间或者是一个柱子。在一开始的时候,相邻的格子之间都有墙隔着。
你想要打通一些相邻房间的墙,使得所有房间能够互相到达。在此过程中,你不能把房子给打穿,或者打通柱子(以及柱子旁边的墙)。同时,你不希望在房子中有小偷的时候会很难抓,所以你希望任意两个房间之间都只有一条通路。现在,你希望统计一共有多少种可行的方案。
Input
第一行两个数分别表示\(n\)和\(m\)。
接下来n行,每行m个字符,每个字符都会是’.’或者’’,其中’.’代表房间,’’代表柱子。
Output
一行一个整数,表示合法的方案数 \(Mod 10^9\)
Sample Input
3 3
...
...
.*.
Sample Output
15
HINT
对于前\(100%\)的数据,\(n,m \le 9\)
Solution
需要運用到Matrix-Tree定理和一些行列式的知識(見前面的<行列式學習筆記>).
下面我们介绍一种新的方法——Matrix-Tree定理(Kirchhoff矩阵-树定理)。Matrix-Tree定理是解决生成树计数问题最有力的武器之一。它首先于1847年被Kirchhoff证明。在介绍定理之前,我们首先明确几个概念:
- \(G\)的度数矩阵\(D[G]\)是一个\(n * n\)的矩阵,并且满足:当\(i \ne j\)时,\(d_{i, j}\)=0;当\(i = j\)时,\(d_{i, j}\)等于\(v_i\)的度数。
- \(G\)的邻接矩阵\(A[G]\)也是一个\(n * n\)的矩阵, 并且满足:如果\(v_i\)、\(v_j\)之间有边直接相连,则\(a_{i, j}=1\),否则为\(0\)。
我们定义\(G\)的Kirchhoff矩阵(也称为拉普拉斯算子)\(C[G]\)为\(C[G] = D[G] - A[G]\),则Matrix-Tree定理可以描述为:\(G\)的所有不同的生成树的个数等于其Kirchhoff矩阵\(C[G]\)任何一个\(n - 1\)阶主子式的行列式的绝对值。所谓\(n - 1\)阶主子式,就是对于\(r (1 \le r \le n)\),将\(C[G]\)的第\(r\)行、第\(r\)列同时去掉后得到的新矩阵,用\(Cr[G]\)表示。
實際上, 這個矩陣並不需要由兩個相減得來(畢竟Kirchoff矩陣一般只會考它的結論). 可以直接枚舉兩個點\(u\)和\(v\), 假如\(u \to v\)有一條連邊, 則矩陣中\(det[u][u] ++\)同時\(det[u][v] --\). 這樣得到的結果相當於這兩個矩陣相減. 然後再這個行列式上跑類似於高斯消元的東西就可以了.
注意: 一定要把任意的第\(r\)行第\(r\)列去掉后再求出行列式的值, 否则求出的答案永远都是\(0\)
還有一點要注意的:
然后关于取模的高斯消元,有两种实现方法:
①求乘法逆元
②欧几里得算法,同时把其他项也给消掉。注意在交换的时候,根据行列式的性质,取负号。注意全部变成正数再进行gcd。
差不多就是這些了.
#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
using namespace std;
inline void read(int &x)
{
x = 0;
int flag = 1;
char c;
while(! isdigit(c = getchar()))
if(c == '-')
flag *= - 1;
while(isdigit(c))
x = x * 10 + c - '0', c = getchar();
x *= flag;
}
inline void read(char &c)
{
while(! isgraph(c = getchar()));
}
void println(int x)
{
if(x < 0)
putchar('-'), x *= - 1;
if(x == 0)
putchar('0');
int ans[1 << 5], top = 0;
while(x)
ans[top ++] = x % 10, x /= 10;
for(; top; top --)
putchar(ans[top - 1] + '0');
putchar('\n');
}
const int N = 1 << 4, M = 1 << 4;
const long long MOD = (long long)1e9;
const int movesLikeJagger[4][2] = {{1, 0}, {- 1, 0}, {0, 1}, {0, - 1}};
int n, m;
char s[N][M];
int id[N][M];
long long det[N * M][N * M];
inline int legal(int x, int y)
{
return x && x <= n && y && y <= m && s[x][y] == '.';
}
inline void add(int u, int v)
{
det[u][u] ++, det[u][v] --;
}
long long calculate(int n)
{
for(int i = 0; i < n; i ++)
for(int j = 0; j < n; j ++)
det[i][j] = (det[i][j] + MOD) % MOD;
long long sign = 0;
for(int i = 0; i < n; i ++)
{
int j;
for(j = i; j < n; j ++)
if(det[j][i])
break;
if(j == n)
continue;
if(i != j)
{
sign ^= (i != j);
for(int k = i; k < n; k ++)
swap(det[j][k], det[i][k]);
}
for(int j = i + 1; j < n; j ++)
while(det[j][i])
{
int tmp = det[j][i] / det[i][i];
for(int k = i; k < n; k ++)
det[j][k] = (det[j][k] - det[i][k] * tmp % MOD + MOD) % MOD;
if(! det[j][i])
break;
sign ^= 1;
for(int k = i; k < n; k ++)
swap(det[i][k], det[j][k]);
}
}
long long ret = 1;
for(int i = 0; i < n; i ++)
ret = (ret * det[i][i]) % MOD;
if(sign)
ret = (MOD - ret) % MOD;
return ret;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("BZOJ4031.in", "r", stdin);
freopen("BZOJ4031.out", "w", stdout);
#endif
read(n), read(m);
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++)
read(s[i][j]);
int cnt = 0;
for(int i = 1; i <= n ; i ++)
for(int j = 1; j <= m; j ++)
if(s[i][j] == '.')
id[i][j] = cnt ++;
memset(det, 0, sizeof(det));
for(int x = 1; x <= n; x ++)
for(int y = 1; y <= m; y ++)
if(legal(x, y))
for(int i = 0; i < 4; i ++)
{
int nextX = x + movesLikeJagger[i][0], nextY = y + movesLikeJagger[i][1];
if(legal(nextX, nextY))
add(id[x][y], id[nextX][nextY]);
}
int ans = calculate(cnt - 1);
println(ans);
}