题目大意
「Codeplus 2」白金元首与独舞(Luogu 4033)
给定 $n \times m$ 的棋盘,每个格子上要么有一个指向上、下、左、或右的箭头,要么是空白的。现在你要在所有空白的格子中填入箭头,使得从图中任意一个格子出发,每次按照当前格子中箭头的方向走一步,都一定能走到棋盘的外面。问一共有多少种填法$\bmod 10^9 + 7$ 的结果。
数据范围:$n, m \le 200$,空格子的个数 $k \le 300$。
思路分析
首先我们将棋盘外的空间视作一个虚点。发现如果一个填好的棋盘是合法的,那么我们将每个格子向它指向的格子连一条有向边(如果它指向棋盘外,那么就连向虚点)后,一定会形成一个以该虚点为根的内向树。于是这个问题就转化成了一个生成树计数的问题了。将每个格子向四周连四条边,其他格子照常连边,然后求建好的图以虚点为根的内向树个数即可。
这里简要讲解一下生成树计数的过程。对于一张无向图,我们构造矩阵 $K = D - A$,其中 $K$ 是基尔霍夫矩阵,$D$ 是度数矩阵,$A$ 是邻接矩阵。根据矩阵树定理,这张图的不同生成树个数等于其基尔霍夫矩阵 $K$ 的任意一个主余子式,其中主余子式表示去掉第 $i$ 行第 $i$ 列的余子式,即 $M_{i, i}$。对于一张有向图,如果要求的是外向树个数,那么度数矩阵中的度数就是入度;如果要求的是内向树个数,那么度数矩阵中的度数就是出度。之后,如果要求以 $x$ 为根的生成树个数,那么答案就是 $M_{x, x}$。
但是直接这么做点数是 $O(nm)$ 的,时间复杂度为 $O(n^3 m^3)$。我们考虑缩点,从每个点的相邻的格子开始一直走,知道碰到另外一个点或者走出棋盘。那么,我们从原来的点向走到的线连一条边,然后求生成树个数即可。这样点数就减少到了 $O(k)$,求生成树个数的部分的复杂度为 $O(k^3)$,但是之前建边的复杂度是 $O(k n m)$ 的,不够优秀。我们使用记忆化搜索,就把建边优化到了 $O(nm)$。这样总时间复杂度 $O(nm + k^3)$,可以通过本题。
听说还有个 $O(2^{\sqrt k})$ 的做法?太神仙了,我怎么可能会啊!
代码实现
1 |
|
2 |
|
3 | using namespace std; |
4 | |
5 | const int maxn = 200, maxm = 300, mod = 1e9 + 7; |
6 | const int dx[] = { -1, 1, 0, 0 }, dy[] = { 0, 0, -1, 1 }; |
7 | int T, n, m, cnt, a[maxn + 3][maxn + 3], id[maxn + 3][maxn + 3], A[maxm + 3][maxm + 3]; |
8 | int kx[maxm + 3], ky[maxm + 3], vis[maxn + 3][maxn + 3], ter[maxm + 3][4]; |
9 | bool in[maxn + 3][maxn + 3]; |
10 | char s[maxn + 3]; |
11 | |
12 | int qpow(int a, int b) { |
13 | int c = 1; |
14 | for (; b; b >>= 1, a = 1ll * a * a % mod) { |
15 | if (b & 1) c = 1ll * a * c % mod; |
16 | } |
17 | return c; |
18 | } |
19 | |
20 | void print(int A[maxm + 3][maxm + 3], int n) { |
21 | for (int i = 1; i <= n; i++) { |
22 | for (int j = 1; j <= n; j++) { |
23 | printf("%d%c", A[i][j], " \n"[j == n]); |
24 | } |
25 | } |
26 | } |
27 | |
28 | int solve(int A[maxm + 3][maxm + 3], int n) { |
29 | int sgn = 1; |
30 | for (int i = 1; i <= n; i++) { |
31 | if (!A[i][i]) { |
32 | bool flag = false; |
33 | for (int j = i + 1; j <= n; j++) { |
34 | if (A[j][i]) { |
35 | sgn = -sgn; |
36 | for (int k = i; k <= n; k++) { |
37 | swap(A[i][k], A[j][k]); |
38 | } |
39 | flag = true; |
40 | break; |
41 | } |
42 | } |
43 | if (!flag) { |
44 | return 0; |
45 | } |
46 | } |
47 | for (int j = i + 1; j <= n; j++) { |
48 | int x = 1ll * A[j][i] * qpow(A[i][i], mod - 2) % mod; |
49 | for (int k = i; k <= n; k++) { |
50 | A[j][k] = (A[j][k] - 1ll * A[i][k] * x) % mod; |
51 | A[j][k] < 0 ? A[j][k] += mod : 0; |
52 | } |
53 | } |
54 | } |
55 | int ans = sgn < 0 ? sgn + mod : sgn; |
56 | for (int i = 1; i <= n; i++) { |
57 | ans = 1ll * ans * A[i][i] % mod; |
58 | } |
59 | return ans; |
60 | } |
61 | |
62 | int dfs(int x, int y) { |
63 | if (x < 1 || x > n || y < 1 || y > m) { |
64 | return cnt + 1; |
65 | } |
66 | if (a[x][y] == -1) { |
67 | return id[x][y]; |
68 | } |
69 | if (vis[x][y]) { |
70 | return vis[x][y]; |
71 | } |
72 | if (in[x][y]) { |
73 | return -1; |
74 | } |
75 | in[x][y] = true; |
76 | int t = a[x][y]; |
77 | return vis[x][y] = dfs(x + dx[t], y + dy[t]); |
78 | } |
79 | |
80 | int main() { |
81 | scanf("%d", &T); |
82 | while (T--) { |
83 | scanf("%d %d", &n, &m); |
84 | cnt = 0; |
85 | for (int i = 1; i <= n; i++) { |
86 | scanf("%s", s + 1); |
87 | for (int j = 1; j <= m; j++) { |
88 | s[j] == 'U' ? a[i][j] = 0 : |
89 | s[j] == 'D' ? a[i][j] = 1 : |
90 | s[j] == 'L' ? a[i][j] = 2 : |
91 | s[j] == 'R' ? a[i][j] = 3 : |
92 | a[i][j] = -1; |
93 | if (a[i][j] == -1) { |
94 | id[i][j] = ++cnt, kx[cnt] = i, ky[cnt] = j; |
95 | } |
96 | } |
97 | } |
98 | for (int i = 1; i <= n; i++) { |
99 | for (int j = 1; j <= m; j++) { |
100 | in[i][j] = false, vis[i][j] = 0; |
101 | } |
102 | } |
103 | bool flag = false; |
104 | for (int i = 1; i <= n; i++) { |
105 | for (int j = 1; j <= m; j++) { |
106 | if (dfs(i, j) == -1) { |
107 | flag = true; |
108 | break; |
109 | } |
110 | } |
111 | if (flag) { |
112 | break; |
113 | } |
114 | } |
115 | if (flag) { |
116 | puts("0"); |
117 | continue; |
118 | } |
119 | for (int i = 1; i <= cnt; i++) { |
120 | for (int j = 0; j < 4; j++) { |
121 | ter[i][j] = dfs(kx[i] + dx[j], ky[i] + dy[j]); |
122 | } |
123 | } |
124 | for (int i = 1; i <= cnt; i++) { |
125 | for (int j = 1; j <= cnt; j++) { |
126 | A[i][j] = 0; |
127 | } |
128 | } |
129 | for (int i = 1; i <= cnt; i++) { |
130 | for (int j = 0; j < 4; j++) { |
131 | if (i != ter[i][j]) { |
132 | A[i][i]++; |
133 | if (ter[i][j] != cnt + 1) { |
134 | A[i][ter[i][j]]--; |
135 | } |
136 | } |
137 | } |
138 | } |
139 | for (int i = 1; i <= cnt; i++) { |
140 | for (int j = 1; j <= cnt; j++) { |
141 | if (A[i][j] < 0) { |
142 | A[i][j] += mod; |
143 | } |
144 | } |
145 | } |
146 | printf("%d\n", solve(A, cnt)); |
147 | } |
148 | return 0; |
149 | } |