题目描述
你突然有了一个大房子,房子里面有一些房间。事实上,你的房子可以看做是一个包含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;
}