您的位置:首页 > 其它

[bzoj4031][高斯消元][矩阵树]小Z的房间

2018-03-01 11:41 253 查看
4031: [HEOI2015]小Z的房间

Time Limit: 10 Sec Memory Limit: 256 MB

Submit: 1455 Solved: 734

[Submit][Status][Discuss]

Description

你突然有了一个大房子,房子里面有一些房间。事实上,你的房子可以看做是一个包含n*m个格子的格状矩形,每个格子是一个房间或者是一个柱子。在一开始的时候,相邻的格子之间都有墙隔着。

你想要打通一些相邻房间的墙,使得所有房间能够互相到达。在此过程中,你不能把房子给打穿,或者打通柱子(以及柱子旁边的墙)。同时,你不希望在房子中有小偷的时候会很难抓,所以你希望任意两个房间之间都只有一条通路。现在,你希望统计一共有多少种可行的方案。

Input

第一行两个数分别表示n和m。

接下来n行,每行m个字符,每个字符都会是’.’或者’’,其中’.’代表房间,’’代表柱子。

Output

一行一个整数,表示合法的方案数 Mod 10^9

Sample Input

3 3





.*.

Sample Output

15

HINT

对于前100%的数据,n,m<=9

Source

矩阵树的话,反正我不会证矩阵树定理是吧,那我们理解一下行列式好了。

行列式

我们设现在有一个n*n的方阵(我们的基尔霍夫矩阵一定是个方阵)

行列式=∑p(p是一个n的排列)(−1)p的逆序对数∗∏ia[i][pi]∑p(p是一个n的排列)(−1)p的逆序对数∗∏ia[i][pi]

推论0:

矩阵的和的行列式和矩阵的行列式的和 不相等

推论1:

如果2个矩阵只有某一行不同,那么这两个矩阵的行列式的和等于这一行相加,其他行不变的矩阵的行列式。这个用行列式的定义式分配律一下就行了。

推论2:

把行列式两行交换,行列式的值取反。这样考虑,你本来是

∑p(p是一个n的排列)(−1)p的逆序对数∗(∏i!=px,i!=pya[i][pi])∗a[px][px]∗a[py][py]∑p(p是一个n的排列)(−1)p的逆序对数∗(∏i!=px,i!=pya[i][pi])∗a[px][px]∗a[py][py]

现在是

∑p(p是一个n的排列)(−1)p的逆序对数∗(∏i!=px,i!=pya[i][pi])∗a[py][px]∗a[px][py]∑p(p是一个n的排列)(−1)p的逆序对数∗(∏i!=px,i!=pya[i][pi])∗a[py][px]∗a[px][py]

再变成a[px][py]*a[py][px],我们不去算这个式子,我们考虑把第一个式子前面的p中的px和py交换了,那么两个式子中后面的部分完全相同,而交换px,py改变的逆序对数一定是奇数个的,这个分3块随便讨论一下即可。所以就相当于是式子取反了。

推论3:

一个矩阵的两行完全相等的话,矩阵行列式为0。和推论2一模一样。把px,py处理一下,容易发现px行全部和py行相等,但是py行也是逆序对会变,所以贡献都没了。

推论4:

一个矩阵的某一行*x,行列式*x,定义式提一下公因式就行了。

推论5:

每一行的值的和是0的时候,行列式的值为0。(不会证)

推论6:

一个矩阵的某一行乘某个数加到另一行上面,显然行列式不变。

考虑这个新矩阵的行列式,由推论1容易分成2个矩阵的行列式的和的形式。

那么其中一个矩阵是一行x,另一行是yx,根据推论4,先把yx除成x,这个矩阵根据推论3行列式是0,就算再*y回去,也是0。所以提供贡献的是原矩阵。

那我们现在可以*一行加到另一行了,那就说明可以高消了是吧。注意一下就是根据推论2,你交换2行的时候需要乘-1。

那么我们怎么求解一个矩阵的行列式呢,我们先把这个矩阵消成一个上三角矩阵,然后把对角线乘起来就可以了。容易发现一个上三角矩阵的行列式的值就是其对角线的乘积(我发现不了)。

矩阵树(教你怎么背诵内容)

基尔霍夫矩阵就是度数矩阵-邻接矩阵,这个在重边的时候也成立。在有向边的情况下,度数矩阵指入度。我们称Mi,j是一个矩阵的余子式,他表示的是矩阵去掉i行和j列下的行列式的值。

容易发现基尔霍夫矩阵是每一行的和都为0的矩阵。所以他的行列式为0。但是他的任意一个余子式的行列式都是对应图的生成树的个数。而在有向图中,就没有树的概念了,而是一个树形图。比如i是根,则Mi,i的行列式是对应图的树形图的个数。

完结撒花

sol:

本来写高消就是为了矩阵树准备的。结果前面高消还写了挺多题的。感觉也还不错,异或方程组和同余方程组还是见见的好。

我本来是不想把高消取模再拉出来写了,结果刚写矩阵树就发现了一个良心出题人,1e9的模数让我们逆元不能用了。那如果没有逆元又要取模的话要怎么办呢?我们考虑辗转相除,维护a[i][i]和a[j][i],目的是用a[i][i]把a[j][i]消掉,从辗转相除的角度考虑肯定会有一个被消成0的。那我们考虑怎么做这个过程。好像不用考虑,直接倍数做一下模一下就行了。

#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
using namespace std;
typedef long long ll;
typedef double s64;
int n,m;
const int N=100;
const int pyz=1e9;
int tot,num

,c

;
char sr

;
const int dx[4]={0,0,1,-1};
const int dy[4]={1,-1,0,0};
int ans=1;
inline void guass()
{
/*  for(int i=1;i<=tot;++i){
for(int j=1;j<=tot;++j)
printf("%d ",c[i][j]);
printf("\n");
}*/

tot--;
for(int i=1;i<=tot;++i)
{
int row;
for(row=i;row<=tot;++row) if(c[row][i]) break;
if(row!=i)
{
for(int j=1;j<=tot;++j) swap(c[row][j],c[i][j]);
ans*=-1;
}
for(int j=i+1;j<=tot;++j)
while(c[j][i])
{
int t=c[j][i]/c[i][i];
for(int k=1;k<=tot;++k)
c[j][k]=(c[j][k]+pyz-(ll)t*c[i][k]%pyz)%pyz;
if(!c[j][i]) break;
ans*=-1;
for(int k=1;k<=tot;++k) swap(c[i][k],c[j][k]);
}
}
ans=(ans+pyz)%pyz;
for(int i=1;i<=tot;++i)
ans=(ll)ans*c[i][i]%pyz;
}
int main()
{
//  freopen("4031.in","r",stdin);
//  freopen(".out","w",stdout);
cin>>n>>m;
for(int i=1;i<=n;++i)
scanf("%s",sr[i]+1);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
if(sr[i][j]=='.')
num[i][j]=++tot;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
if(sr[i][j]=='.')
{
int x,y;
for(int k=0;k<=3;++k)
{
x=i+dx[k];
y=j+dy[k];
if(1<=x&&x<=n&&1<=y&&y<=m&&sr[x][y]!='*')
{
c[num[i][j]][num[i][j]]++;
c[num[i][j]][num[x][y]]--;
}
}
}
guass();
printf("%d\n",ans);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: