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证明。在介绍定理之前,我们首先明确几个概念:

  1. \(G\)的度数矩阵\(D[G]\)是一个\(n * n\)的矩阵,并且满足:当\(i \ne j\)时,\(d_{i, j}\)=0;当\(i = j\)时,\(d_{i, j}\)等于\(v_i\)的度数。
  2. \(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);
}
posted @ 2017-02-20 22:11  Zeonfai  阅读(194)  评论(0编辑  收藏  举报