题意
https://www.luogu.org/problemnew/show/P4111
题解
前置知识:矩阵树定理
不要问证明,我不会,用就完事了(反正一般也不会用到)
因为矩阵树定理就是求一张 $n$ 个点的简单无向图的生成树个数,时间复杂度为 $O(n^3)$,再看看这道题的数据范围,$n,mle 9$,直接矩阵树定理就可以了……
注意因为我们要求方案数,这个数可能很大,而我们用高斯消元把矩阵消成上三角的话,由于要用 $double$,会出现精度误差,这种小数运算的误差在这种求方案数的题中是不允许的(因为方案数就是一个准确的整数,没有保留几位小数之说)。所以这里采用辗转相除法把高斯消元的非整数倍消元 转成多次整数倍消元,具体实现见代码,就是对矩阵的两行进行类似于辗转相除的操作。总时间复杂度就在普通 $O(n^3)$ 高消基础上 乘上每次辗转相除的复杂度(辗转相除的复杂度是 $O(辗转相除的两数中较小数在斐波那契数列的第几项)$)。
upd:时间复杂度应该是 $O(n^3+n^2 P)$($P$ 是模数,即值域),而不是网上大部分题解说的 $O(n^3 log{P})$。证明(因为辗转相除的次数接近 $log$,我们可以近似地用 $log$ 表示辗转相除的复杂度):

1 #include<bits/stdc++.h> 2 #define ll long long 3 #define N 110 4 #define p 1000000000 5 using namespace std; 6 const int dx[2] = {-1, 0}, dy[2] = {0, -1}; 7 inline int read(){ 8 int x = 0; bool f = 1; char c = getchar(); 9 for(; !isdigit(c); c=getchar()) if(c == '-') f = 0; 10 for(; isdigit(c); c=getchar()) x = (x<<3) + (x<<1) + (c^'0'); 11 if(f) return x; 12 return 0 - x; 13 } 14 int n, m, tot, id[N][N]; 15 bool e[N][N]; 16 ll d[N][N]; 17 char s[N]; 18 ll solve(){ 19 int n = tot - 1; 20 bool tr = 0; 21 ll ans = 1; 22 23 for(int i=1; i<=n; ++i){ 24 for(int j=i+1; j<=n; ++j){ 25 while(d[j][i]){ 26 ll tmp = d[i][i] / d[j][i]; 27 for(int k=1; k<=n; ++k){ //辗转相除 28 d[i][k] = (d[i][k] - tmp * d[j][k] % p + p) % p; 29 swap(d[i][k], d[j][k]); 30 } 31 tr ^= 1; 32 } 33 } 34 if(!d[i][i]) return 0; 35 ans = ans * d[i][i] % p; 36 } 37 if(tr) ans = p - ans; //原型是 ans = -ans,交换奇数次行时,行列式值要取负 38 return ans; 39 } 40 int main(){ 41 n = read(), m = read(); 42 for(int i=1; i<=n; ++i){ 43 scanf("%s", s+1); 44 for(int j=1; j<=m; ++j) 45 if(s[j] == '.') e[i][j] = 1; 46 } 47 for(int i=1; i<=n; ++i){ 48 for(int j=1; j<=m; ++j){ 49 if(!e[i][j]) continue; 50 id[i][j] = ++tot; 51 int u = id[i][j]; 52 for(int k=0; k<2; ++k){ 53 int x = i + dx[k], y = j + dy[k]; 54 if(!e[x][y]) continue; 55 int v = id[x][y]; 56 ++d[u][u], ++d[v][v], 57 d[u][v] = (d[u][v] - 1 + p) % p, d[v][u] = (d[v][u] - 1 + p) % p; 58 } 59 } 60 } 61 cout << solve() << endl; 62 return 0; 63 }