JOISC 2016 Day 1 棋盘游戏

题意

JOI 君有一个棋盘,棋盘上有 \(N\) 行 \(3\) 列 的格子。JOI 君有若干棋子,并想用它们来玩一个游戏。初始状态棋盘上至少有一个棋子,也至少有一个空位。

游戏的目标是:在还没有放棋子的格子上依次放棋子,并填满整个棋盘。在某个格子上放置棋子必须满足以下条件之一:

  1. 这个格子的上下一格都放有棋子;
  2. 这个格子的左右一格都放有棋子。

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;
}
上一篇:快手扫码登陆


下一篇:「JOISC 2021 Day1」饮食区