那是我们的影子
题目描述
由 $3 \times n$ 个单元格构成的 $3$ 行 $n$ 列异形数独规则如下:
- 每一个单元格都需要填入 $1$ 到 $9$ 之间的整数;
- 任意一个 $3 \times 3$ 的子矩阵中都不包含重复的数字;
现在,牛可乐已经填入了一些数字,请你在此基础上帮助他计算,这个异形数独一共可以构造出多少个不同的合法解。由于答案可能很大,请输出答案对 $10^9+7$ 取模后的结果。
输入描述:
每个测试文件均包含多组测试数据。第一行输入一个整数 $T\,(1 \leq T \leq 100)$ 代表数据组数,每组测试数据描述如下: 第一行输入一个整数 $n\,(3\leq n \leq 10^5)$ 代表异形数独的列数。
此后三行,每行输入一个长度为 $n$ 、仅由 $1 \sim 9$ 这九个数字和字符 $\text{'?'}$ 构成的字符串 $s$ ,代表牛可乐填入部分数字后的矩阵。其中,字符 $\text{'?'}$ 代表该单元格尚未填入数字。
除此之外,保证单个测试文件的 $n$ 之和不超过 $3 \times 10^5$ 。
输出描述:
对于每组测试数据,新起一行。输出一个整数,代表这个异形数独一共可以构造出多少个不同的合法解。由于答案可能很大,请输出答案对 $10^9+7$ 取模后的结果。
示例1
输入
4
6
1???56
456789
7891??
3
???
???
?11
3
123
456
789
3
723
18?
?9?
输出
2
0
1
6
说明
于第一组测试数据,合法的情况有:$\displaylines{\begin{matrix} \begin{bmatrix} 1 & {\color{orange}{\bf 2}} & {\color{orange}{\bf 3}} & {\color{orange}{\bf 4}} & 5 & 6 \\ 4 & 5 & 6 & 7 & 8 & 9 \\ 7 & 8 & 9 & 1 & {\color{orange}{\bf 2}} & {\color{orange}{\bf 3}} \\ \end{bmatrix} \end{matrix}}$、$\displaylines{\begin{matrix} \begin{bmatrix} 1 & {\color{orange}{\bf 3}} & {\color{orange}{\bf 2}} & {\color{orange}{\bf 4}} & 5 & 6 \\ 4 & 5 & 6 & 7 & 8 & 9 \\ 7 & 8 & 9 & 1 & {\color{orange}{\bf 3}} & {\color{orange}{\bf 2}} \\ \end{bmatrix} \end{matrix}}$ 两种。
对于第二组测试数据,由于初始的矩阵中存在两个 $\text{'1'}$,所以无论如何构造都不合法。
对于第三组测试数据,不需要填入任何数字,所以只有唯一一种合法解。
解题思路
先来点吐槽,好久没写博客了,前段时间有挺多事情要做的甚至都没怎么做题。又爆金币打今年的牛客,前两场体验极差,差不多全是思维构造贪心题,而我最不擅长就是这类题,所以做起来非常痛苦。而且很幽默的是这两场总会有一道差不多过了 1k 人的题而偏偏就我不会做,唐完了。
想着直接无脑状压 dp 水过去的,结果发现代码很难写,而且简化状态后好像还是会超时。
这题难在要观察出非常关键的性质,看不出来基本就别想做了。考虑任意一个 $3 \times 3$ 的矩阵,由于恰好出现 $1 \sim 9$ 不同的数字,因此将矩阵向右平移一个单位后,平移后矩阵的最后一列出现的数字和平移前矩阵第一列出现的数字应该要相同(数字的排列可以不同)。所以所有的列会根据列号模 $3$ 的结果分成 $3$ 类,每一类中的列出现的数字都应该是相同的。
此外如果整个 $3 \times n$ 的矩阵是合法的,那么必然要满足下面的条件:
- 在一列中每种数字只能出现一次。
- 每个数字只能出现在某一类的列中。
- 某一类的列中出现的数字恰好有三种。
所以我们可以先判断给定的矩阵是否有解,如果不存在解则直接输出 $0$ 即可。
对于每一列,记 $c_i$ 表示第 $i$ 列中 ?
的数量。对于每一类,记 $s_i$ 为二进制状态,如果第 $k$ 位为 $1$ 则表示第 $i$ 类的列中出现了数字 $k$,$\mathrm{count}(s_i)$ 就表示 $s_i$ 为 $1$ 的位数。因此如果有解,必然有 $\mathrm{count}(s_i) \leq 3$ $(i = 0,1,2)$,而且 $s_i \, \& \, s_j = 0$ $(0 \leq i < j \leq 2)$。记 $r = 9 - \sum{\mathrm{count}(s_i)}$,首先给每一类分配剩余的数字,方案数为 $C_{r}^{3 - \mathrm{count}(s_0)} \times C_{r - (3 - \mathrm{count}(s_0))}^{3 - \mathrm{count}(s_1)} \times C_{r - (3 - \mathrm{count}(s_0)) - (3 - \mathrm{count}(s_1))}^{3 - \mathrm{count}(s_2)}$。然后由于每一列中数字可以放在任意一个为 ?
的格子,因此当确定了每一类的分配方案后,排列的数量为 $\prod\limits_{i=0}^{n-1}{c_i !}$。
因此总的方案数就是 $C_{r}^{3 - \mathrm{count}(s_0)} \times C_{r - (3 - \mathrm{count}(s_0))}^{3 - \mathrm{count}(s_1)} \times C_{r - (3 - \mathrm{count}(s_0)) - (3 - \mathrm{count}(s_1))}^{3 - \mathrm{count}(s_2)} \times \prod\limits_{i=0}^{n-1}{c_i !}$。
AC 代码如下,时间复杂度为 $O(n)$:
#include <bits/stdc++.h>
using namespace std;typedef long long LL;const int N = 3e5 + 5, mod = 1e9 + 7;char g[3][N];
int c[N];LL C(int a, int b) {LL ret = 1;for (int i = 1, j = a; i <= b; i++, j--) {ret = ret * j / i;}return ret;
}void solve() {int n;cin >> n;for (int i = 0; i < 3; i++) {cin >> g[i];}array<int, 3> s({0});memset(c, 0, n << 2);for (int i = 0; i < n; i++) {for (int j = 0, t = 0; j < 3; j++) {if (g[j][i] == '?') {c[i]++;}else {int x = g[j][i] - '1';if (t >> x & 1) {cout << 0 << '\n';return;}t |= 1 << x;s[i % 3] |= 1 << x;}}}if (s[0] & s[1] || s[0] & s[2] || s[1] & s[2]) {cout << 0 << '\n';return;}for (int i = 0; i < 3; i++) {s[i] = __builtin_popcount(s[i]);if (s[i] > 3) {cout << 0 << '\n';return;}}int ret = 1;for (int i = 0, r = 9 - s[0] - s[1] - s[2]; i < 3; i++) {ret = ret * C(r, 3 - s[i]) % mod;r -= 3 - s[i];}for (int i = 0; i < n; i++) {if (c[i] == 2) ret = 2ll * ret % mod;else if (c[i] == 3) ret = 6ll * ret % mod;}cout << ret << '\n';
}int main() {int t;cin >> t;while (t--) {solve();}return 0;
}
参考资料
2025牛客寒假算法基础集训营2 出题人题解:https://blog.nowcoder.net/n/906fd00ff386438b9d63013a3760e73a