题意
JOI 君有一个棋盘,棋盘上有 \(N\) 行 \(3\) 列 的格子。JOI 君有若干棋子,并想用它们来玩一个游戏。初始状态棋盘上至少有一个棋子,也至少有一个空位。
游戏的目标是:在还没有放棋子的格子上依次放棋子,并填满整个棋盘。在某个格子上放置棋子必须满足以下条件之一:
- 这个格子的上下一格都放有棋子;
- 这个格子的左右一格都放有棋子。
JOI 君想知道有多少种从初始状态开始,并达到游戏目标的方案,这个答案可能会非常大。请你帮 JOI 君算出这个答案,并对 \(10^9+7\) 取模。
题解
注意到可以通过上下填的只有第二行。
先判断是否有解。如果第一行或第三行存在两个连续的空格,那么无解,否则有解。那么一三两行可以在任意时刻填棋子。
对第二行的空格连续段分别计算,最后组合即可。
设 \(H(i, r), V(i, r)\) 分别表示考虑到当前连续段第 \(i\) 列,当前中间格在前 \(i\) 列的空格中第 \(r\) 个填,且是依赖左右/上下两边填的方案数。如果四个方向都有棋子,则填上下两边。
设第 \(i\) 列中第一、三行共 \(t\) 个空位。
分三种情况考虑:
-
\(i, i - 1\) 两列均为纵向,则相互不影响。
\[V(i, r) \gets (r-1)^\underline{t}\sum_x V(i - 1, x) \]其中下降幂为 \(i\) 上下的方案数。
-
\(i\) 列为纵向,\(i-1\) 列为横向,则 \(i\) 列必须早于 \(i - 1\) 列填。
\[V(i, r) \gets (r-1)^\underline{t} \sum_{x \ge i - t} H(i - 1, x) \] -
\(i\) 列为横向,\(i-1\) 列为纵向。
此时需要保证填中间格是上下格不能已经全部有棋子,且左边的中间格已经有棋子。
为了避免讨论相对顺序,我们先只加入中间格,然后再加入对顺序没有影响的上下格。
设上下中有 \(c\) 个在中间之前填:
\[H(i, r + c) \gets \sum_{x<r} V(i - 1, x) \cdot E(r - 1, c) \cdot E(\mathrm{cnt} - r, t - c) \cdot t! \]其中 \(r + c\) 为加入上下格后对 \(i\) 的排名的影响,\(\mathrm{cnt}\) 为当前连通块中总空格数,\(E(i, j)\) 表示在 \(i\) 个元素之间的 \(i + 1\) 个空格中放置 \(j\) 个相同物品的方案数。
使用前缀和,总时间复杂度 \(\mathcal O(n^2)\)。
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <numeric>
#include <vector>
#include <cassert>
using namespace std;
#define LOG(f...) fprintf(stderr, f)
#define all(cont) begin(cont), end(cont)
// #define DBG(f...) printf("L %4d: ", __LINE__), printf(f)
#define DBG(f...) void()
char getv() {
char ch;
while (isspace(ch = getchar()));
return ch;
}
using ll = long long;
const int N = 2005;
const int M = 1000000007;
char a[N][3];
int dp[N][N * 3][2];
vector<int> fac, ifac;
int sub(int x, int y) { return x < y ? x - y + M : x - y; }
void inc(int &x, int y) { if ((x += y) >= M) x -= M; }
int power(int x, int y) {
int p = 1;
for (; y; y >>= 1, x = (ll)x * x % M)
if (y & 1) p = (ll)p * x % M;
return p;
}
int inv(int x) { return power(x, M - 2); }
void prefac(int n) {
fac.resize(n + 1); ifac.resize(n + 1);
fac[0] = 1;
for (int i = 1; i <= n; ++i)
fac[i] = (ll)fac[i - 1] * i % M;
ifac[n] = inv(fac[n]);
for (int i = n; i; --i)
ifac[i - 1] = (ll)ifac[i] * i % M;
}
int binom(int n, int m) { return (ll)fac[n] * ifac[m] % M * ifac[n - m] % M; }
int ff(int n, int m) { return (ll)fac[n] * ifac[n - m] % M; }
int main() {
#ifndef ONLINE_JUDGE
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
#endif
int n;
scanf("%d", &n);
prefac(3 * n);
for (int c = 0; c < 3; ++c)
for (int i = 1; i <= n; ++i)
a[i][c] = getv();
for (int i = 1; i <= n; ++i)
if ((a[i][0] == 'x' && (a[i - 1][0] != 'o' || a[i + 1][0] != 'o')) ||
(a[i][2] == 'x' && (a[i - 1][2] != 'o' || a[i + 1][2] != 'o'))) {
puts("0");
return 0;
}
int empt = 0, prod = 1;
int siz = 0;
for (int i = 1; i <= n; ++i) {
int t = int(a[i][0] == 'x') + int(a[i][2] == 'x');
empt += t + int(a[i][1] == 'x');
if (a[i][1] != 'x') // skip
siz = 0;
else if (a[i - 1][1] != 'x') { // init
siz = t + 1;
for (int j = t + 1; j <= siz; ++j)
dp[i][j][0] = ff(j - 1, t);
if (i != 1)
for (int c = 1; c <= t; ++c)
for (int j = t - c + 1; j <= siz - c; ++j)
dp[i][j][1] = (dp[i][j][1] + (ll)binom(j - 1, t - c) * binom(siz - j, c) * fac[t]) % M;
}
else {
int psum0 = dp[i - 1][siz][0], psum1 = dp[i - 1][siz][1];
++siz;
for (int c = 0; c < t; ++c)
for (int j = 1; j <= siz; ++j)
dp[i][j + c][1] = (dp[i][j + c][1] + (ll)dp[i - 1][j - 1][0] * (c == 0 ? 1 : j) % M * binom(siz - j + t - c, t - c) * fac[t]) % M;
siz += t;
for (int j = t + 1; j <= siz; ++j)
dp[i][j][0] = ((ll)psum0 * ff(j - 1, t) + (ll)sub(psum1, dp[i - 1][j - t - 1][1]) * ff(j - 1, t)) % M;
}
if (a[i][1] == 'x')
for (int j = 2; j <= siz + 3; ++j)
inc(dp[i][j][0], dp[i][j - 1][0]), inc(dp[i][j][1], dp[i][j - 1][1]);
if (a[i + 1][1] != 'x' && a[i][1] == 'x') {
int ways = dp[i][siz][0];
if (i != n) inc(ways, dp[i][siz][1]);
prod = (ll)prod * ways % M * ifac[siz] % M;
siz = 0;
}
}
prod = (ll)prod * fac[empt] % M;
printf("%d\n", prod);
return 0;
}