只要你跑的够快,锅就追不上你

0%

「Codeplus 2」白金元首与独舞(矩阵树定理)

题目大意

「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
#include <cstdio>
2
#include <algorithm>
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
}