小Z的房间(矩阵树定理)

题目描述
你突然有了一个大房子,房子里面有一些房间。事实上,你的房子可以看做是一个包含n×m 个格子的格状矩形,每个格子是一个房间或者是一个柱子。在一开始的时候,相邻的格子之间都有墙隔着。你想要打通一些相邻房间的墙,使得所有房间能够互相到达。在此过程中,你不能把房子给打穿,或者打通柱子(以及柱子旁边的墙)。同时,你不希望在房子中有小偷的时候会很难抓,所以你希望任意两个房间之间都只有一条通路。现在,你希望统计一共有多少种可行的方案,答案对 109取模。
输入格式
第一行两个整数 n,m。
接下来 n 行,每行 m 个字符 . 或 * ,其中 . 代表房间,*代表柱子。

输出格式
一行一个整数,表示合法的方案数对 109取模后的值。
输入输出样例
输入 #1
2 2


输出 #1
4
输入 #2
2 2
.
.

输出 #2
0
说明/提示
数据规模与约定
对于 20% 的数据,n,m≤3。
对于 50% 的数据,n,m≤5。
有 40% 的数据源,min(n,m)≤3。
有 30% 的数据,不存在柱子。
对于 100% 的数据,1≤n,m≤9。

由题意可知这道题可转化为:已知有n个点的无向图,求它的生成树数量。
所以这是一道矩阵树定理的题目,解题思路为:将拉普拉斯矩阵去掉任意的一行和一列,得到的矩阵求行列式,即是原图的生成树数量。
所以最后的答案涉及两个部分:
1、对所有可达点进行标号
2、构建拉普拉斯矩阵
3、求n-1阶拉普拉斯矩阵行列式的值(高斯消元算法)
AC代码如下:

1、对可达的点标号比较简单,只要此点可达(此题目中可知条件为 “.” 时进行标号)即可
2、求拉普拉斯矩阵;
给定一个有n个顶点的图G,它的拉普拉斯矩阵 定义为:L=D-A
其中D为图的度矩阵,A为图的邻接矩阵。度矩阵在有向图中,只需要考虑出度或者入度中的一个。
举个栗子:
在这里插入图片描述
图1:用于构造拉普拉斯矩阵的图
在这里插入图片描述

图2:图1的度矩阵
在这里插入图片描述
图3:图1的邻接矩阵
在这里插入图片描述
图4:由图1构造的拉普拉斯矩阵

图片来源百度百科:https://baike.baidu.com/item/%E6%8B%89%E6%99%AE%E6%8B%89%E6%96%AF%E7%9F%A9%E9%98%B5/5583042?fr=aladdin

所以构造拉普拉斯矩阵的方法可以直接用两个数组记录度矩阵和邻接矩阵,最后相减得到拉普拉斯矩阵。或者直接对每个点进行存储:

typedef long long ll;
const int MAXN=10;
ll lm[MAXN*MAXN][MAXN*MAXN];
void add(int x,int y)
{
	lm[x][x]++;
	lm[y][y]++;
	lm[x][y]--;
	lm[y][x]--;
	return ;
}

3、现在已经得到了拉普拉斯矩阵,随便消去一行以及一列,这里选择的是最后一行和最后一列,直接对计数也就是拉普拉斯矩阵的阶数的变量减一即可得到n-1阶行列式。接下来就是求行列式的值,就用到了高斯消元算法。
这里首先要明确行列式的5条性质:
1、转置矩阵,行列式不变;
2、交换两行/列位置,行列式取相反数;
3、对一行/列乘以某数,行列式也乘以某数;
4、用一行的倍数减去另一行,行列式的值不变;
5、一个上三角行列式的值等于对角线的乘积
而高斯消元算法的思路就是把一个行列式削成上三角矩阵(注意,不是对角线矩阵)
高斯消元算法模板:

int gauss(int cnt)
{
	int ans=1;
	for(int i=1;i<cnt;i++)
	{
		for(int j=i+1;j<=cnt;j++)	//对第i列的每一行的元素进行消减,避开对角线的元素
		{
			while(lm[j][i])	//如果不是0在进行消减,否则直接进行下一列即可
			{
				int l=lm[i][i]/lm[j][i];
				//l表示进行辗转相减的次数,例如10 3消减过后成为1 3
				for(int k=1;k<=cnt;k++)
					lm[i][k]=(lm[i][k]-lm[j][k]*l)%mod;
			//对第i行的每一列元素进行消减l此,模mod是题目要求,mod=1e9
				for(int k=1;k<=cnt;k++)
					swap(lm[i][k],lm[j][k]);
				//交换消减的两列,用到了上面提到的矩阵的性质2
				ans*=-1;
				//行列式取相反数,同样是性质2
			}
		}
	}
	for(int i=1;i<=cnt;i++)
		ans=(ans*lm[i][i]%mod+mod)%mod;
	//此时已经削减为上三角矩阵,所以直接求对角线元素之积就可以了
	return ans;
}

注意要建好图,有的地方不是节点,求行列式不要写错,是缩成上三角。下面附此题AC代码:

#include<iostream>

using namespace std;
typedef long long ll;
const int MAXN=10;
const int mod=1e9;
ll id[MAXN][MAXN],lm[MAXN*MAXN][MAXN*MAXN];
//注意对拉普拉斯矩阵(lm[][])开long long
char map[MAXN][MAXN];

void add(int x,int y)
{
	lm[x][x]++;
	lm[y][y]++;
	lm[x][y]--;
	lm[y][x]--;
	return ;
}
int gauss(int cnt)
{
	cnt--;
	int ans=1;
	for(int i=1;i<cnt;i++)
	{
		for(int j=i+1;j<=cnt;j++)
		{
			while(lm[j][i])
			{
				int l=lm[i][i]/lm[j][i];
				for(int k=1;k<=cnt;k++)
					lm[i][k]=(lm[i][k]-lm[j][k]*l)%mod;
				for(int k=1;k<=cnt;k++)
					swap(lm[i][k],lm[j][k]);
				ans*=-1;
			}
		}
	}
	for(int i=1;i<=cnt;i++)
		ans=(ans*lm[i][i]%mod+mod)%mod;
	return ans;
}
int main()
{
	int n,m,cnt;
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			cin>>map[i][j];
			if(map[i][j]=='.')
				id[i][j]=++cnt;
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(map[i][j]=='.'&&map[i+1][j]=='.')
				add(id[i][j],id[i+1][j]);
			if(map[i][j]=='.'&&map[i][j+1]=='.')
				add(id[i][j],id[i][j+1]);
		}
	}
	cout<<gauss(cnt--)<<endl;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值