JOISC 2016 Day 1 棋盤遊戲
阿新 • • 發佈:2021-09-04
題意
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\) 列為橫向,\(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\)
使用字首和,總時間複雜度 \(\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;
}