所谓内向树,就是树上的边是由儿子指向父亲
来看一个题
元首把花园分为 n 行 m 列的网格。每个格子中都可以放置一个标识,指向上、下、左、右四个方向中的任意一个。元首位于一个格子时,会按照其中标识所指的方向进入周围的格子,或者走出花园(即目的格子不在网格之内)。举个例子 —— 对于下面的放置方式,元首从第 3 行第 2 列的格子开始,会沿着以红色标出的路径走出花园;从第 2 行第 2 列的格子开始,则会在以蓝色标出的环路内不断地行走。
← ↑ ←
↓ ← ↑
→ ↓ ←
元首已经设计好了大部分格子的标识。元首用字符 L
、R
、U
、D
分别表示指向左、右、上、下四个方向的标识,用字符 .
表示未决定的格子。现在,元首希望将每个 .
替换为 L
、R
、U
、D
中任意一种,使得从花园中的任意一个格子出发,按照上述规则行走,都可以最终走出花园。
你需要编写程序帮助元首计算替换的不同方案数。两个方案不同当且仅当存在一个格子,使得两个方案中该格子内的标识不同。当然,由于答案可能很大,只需给出方案数除以 109+7 所得的余数即可。
对于此题,我们对于边界外建立一个点(n*m+1),网格每个点向指出去的点连边,未知的点,它的连边方式有4种,求生成树的个数
这题的生成树边是单向的,且都由孩子指向父亲,我们管它叫内向树的生成树计数,然后结论就是:另邻接矩阵为G,出度矩阵为D,那么D-G的任意一个n-1行n-1列子矩阵的行列式就是生成树的个数。
你要问我为什么是这样的,我这么菜,怎么可能会嘛。。。
因为这题的点数太大了,如果直接建立矩阵肯定是不行的,但是我们发现对答案有影响的就是指向未知的点,那么就可以只提取出那些关键点(题目保证了关键点个数只有300个),那么我们在这300个点之间建立关系求行列式就好了。
注意的是一开始需要判断,因为已知方向的点可能已经构成环了,我的方法如下:一开始对于那些已知方向的点,用并查集缩点,然后已知方向的点的联通块只能有一个(指向n*m+1)。
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN = 305;
const int MOD = 1000000007;
int d[MAXN][MAXN], g[MAXN][MAXN], f[MAXN][MAXN];
int n, m, T, i, j, k, fa[MAXN * MAXN], x[MAXN], y[MAXN], cnt, num[MAXN * MAXN];
int dx[4] = {0, 1, 0, -1}, dy[4] = {-1, 0, 1, 0};
char s[MAXN][MAXN];
inline int find(int x)
{
if (x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
inline int point(int x, int y)
{
if (x < 1 || x > n || y < 1 || y > m) return n * m + 1;
else return (x - 1) * m + y;
}
inline int ksm(int x, int y, int z)
{
int b = 1;
while (y)
{
if (y & 1) b = 1ll * b * x % z;
x = 1ll * x * x % z;
y >>= 1;
}
return b;
}
int main()
{
cin >> T;
while (T --)
{
cin >> n >> m;
for(i = 1; i <= m * n + 1; i ++)
fa[i] = i;
cnt = 0;
for(i = 1; i <= n; i ++)
scanf("%s", s[i] + 1);
for(i = 1; i <= n; i ++)
for(j = 1; j <= m; j ++)
{
if (s[i][j] == '.') {x[++cnt] = i; y[cnt] = j; continue;}
int xx = find(point(i, j)), yy;
if (s[i][j] == 'U') yy = find(point(i - 1, j));
if (s[i][j] == 'D') yy = find(point(i + 1, j));
if (s[i][j] == 'L') yy = find(point(i, j - 1));
if (s[i][j] == 'R') yy = find(point(i, j + 1));
fa[xx] = yy;
}
int block = 0;
for(i = 1; i <= n * m + 1; i ++)
if (find(i) == i) block ++;
if (block > cnt + 1) {puts("0"); continue;}
for(i = 1; i <= cnt; i ++)
num[point(x[i], y[i])] = i;
for(i = 1; i <= cnt + 1; i ++)
for(j = 1; j <= cnt + 1; j ++)
g[i][j] = d[i][j] = 0;
num[n * m + 1] = cnt + 1;
for(i = 1; i <= cnt; i ++)
for(j = 0; j < 4; j ++)
{
int xx = dx[j] + x[i], yy = dy[j] + y[i], ff = find(point(xx, yy));
if (i != num[ff]) g[i][num[ff]] ++, d[i][i] ++;
}
for(i = 1; i <= cnt; i ++)
for(j = 1; j <= cnt; j ++)
{
f[i][j] = d[i][j] - g[i][j];
if (f[i][j] < 0) f[i][j] += MOD;
}
int ans = 1;
for(i = 1; i <= cnt; i ++)
{
int k = 0;
for(j = i; j <= cnt; j ++)
if (f[j][i]) {k = j; break;}
if (!k) continue;
if (k != i)
{
ans *= -1;
for(j = 1; j <= cnt; j ++)
swap(f[i][j], f[k][j]);
}
for(j = i + 1; j <= cnt; j ++)
{
int jb = (long long)f[j][i] * ksm(f[i][i], MOD - 2, MOD) % MOD;
for(k = 1; k <= cnt; k ++)
f[j][k] = (f[j][k] - (long long)f[i][k] * jb) % MOD;
}
}
for(i = 1; i <= cnt; i ++)
ans = 1ll * ans * f[i][i] % MOD;
if (ans < 0) ans += MOD;
cout << ans << endl;
}
}