题目
生成树计数(这么总结题意真的好嘛。。。)
分析
Matrix Tree定理裸题
而Matrix Tree定理就是用来生成树计数的
行列式的求法:
对于一个矩阵进行高斯消元
最终案为(-1)^S*(主对角线之积),其中S位行与行之间交换的次数
Matrix-Tree定理:用于计算生成树计数问题
理论基于基尔霍夫(Kirchhoff)矩阵,但是这里不去证明了:
介绍两个矩阵:
度数矩阵D:c[i][i]表示i的度数,其他的都是0
邻接矩阵A:就是常规意义上的0/1邻接矩阵,c[i][i]=0
那么基尔霍夫矩阵就是D-A
生成树的答案就是他的n-1阶主子式的行列式的绝对值,所谓n-1阶主子式,就是去掉第r行r列之后的新矩阵
我相信正常人都会让r=n
辗转相除法处理mod问题
我们最终目的就是为了消元,但是在有mod又没有逆元的时候,我们就要换一种方式
按照辗转相除的思想,我们最终可以让一项为0
具体操作步骤就是t=a[i][j]/a[i+1][j],然后所有元素都减去下一排*t
然后交换两排元素(保证当前排最大),继续重复操作
注意这种方法用在行列式上,辗转相除带来的交换也算是交换。
由于mod没有负数,所以必须维护符号位,如果是负数,那么答案就是mod-ans
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn=12,mod=1e9;
LL A[maxn*maxn][maxn*maxn];
int n,m,cnt;
char s[maxn][maxn];
int id[maxn][maxn];
int dx[]={1,-1,0,0};
int dy[]={0,0,1,-1};
void Init()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%s",s[i]+1);
int ni,nj;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(s[i][j]=='.')id[i][j]=++cnt;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
for(int k=0;k<4;k++)
{
ni=i+dx[k],nj=j+dy[k];
if(!id[ni][nj])continue;
A[id[i][j]][id[i][j]]++;
A[id[ni][nj]][id[i][j]]--;
}
}
}
}
int Gauss(int n,int m)
{
for(int i=1;i<=n+1;i++)
for(int j=1;j<=m+1;j++)
if(A[i][j]<0)A[i][j]+=mod;
int i=1,j=1,k,r,c;
bool mark=0;
while(i<=m && j<=n)
{
for(r=i,k=i+1;k<=m;k++)
if(abs(A[k][j])>abs(A[r][j]))r=k;
if(abs(A[r][j])>0)
{
if(i!=r)
{
mark^=1;
for(c=j;c<=n;c++)swap(A[r][c],A[i][c]);
}
for(k=i+1;k<=m;k++)
{
while(A[k][j])
{
int tmp=A[k][j]/A[i][j];
for(c=j;c<=n;c++)A[k][c]=(A[k][c]-A[i][c]*tmp%mod+mod)%mod;
if(!A[k][j])break;
mark^=1;
for(c=j;c<=n;c++)swap(A[k][c],A[i][c]);
}
}
i++;
}
j++;
}
LL ret=1;
for(int i=1;i<=n;i++)
ret=(ret*A[i][i])%mod;
if(mark)
return (mod-ret)%mod;
return ret;
}
int main()
{
//freopen("in.txt","r",stdin);
Init();
printf("%d\n",Gauss(cnt-1,cnt-1));
return 0;
}