#2731. 「JOISC 2016 Day 1」棋盘游戏

#2731. 「JOISC 2016 Day 1」棋盘游戏

题目链接:

Link

题目分析:

称之为神仙 dp 不为过。(至少我是什么认为的,可能是我太菜了 qwq)

首先判断无解的情况,很显然无解有两个条件

  1. 四个角有空格
  2. 第一行和最后一行存在着连续两个空格

应该不用解释了吧。

然后就是这个题去分析一些性质了:

首先第一行和第三行的选择是相互独立的,因此我们只需要去 dp 中间一行的状态即可。

然后考虑如何设置这个 dp 状态,根据上面的分析,肯定是要按照列来进行转移的,然后我们可以设 \(dp[i][j]\) 表示前 \(i\) 列,当前选的是第 \(j\) 次的方案数。

但是考虑到当前列的转移是有两种情况的,因此我们还需要记一维表示当前是横着还是竖着进行转移的。所以当前的 dp 式子为 $dp[i][j][0/1] $ 表示前 \(i\) 列,当前是第 \(j\) 个选的,当前选的情况是上下/左右转移。

考虑怎么进行转移。首先考虑中间已经确定的情况,那么此时就相当于不同的联通块了,那么直接把所有的状态都转移到下一位就可以了。

然后分两种情况:

  1. 当前是竖着进行转移,那么如果前面是横着进行转移的话,肯定是要在上一位的后面进行转移的。

    如果当前是竖着进行转移,上一位也是竖着进行转移的话,那么都是相互独立的,可以插到前面的状态里面。

  2. 如果当前是横着进行转移的话,第一种就是前面的竖着的必须要选上才行。

    第二种就是横着竖着都可以的情况,为了不重复计算,我们强制算在竖着选的里面,也就是都在后面进行选,那么剩下的情况就是上面的在前面选,剩下的在后面选,最后因为上下可以颠倒所以乘 2。

那么情况就讨论完了,因为是一个前缀的转移,所以我们利用前缀和进行优化即可。

Code:

//editor : DRYAYST
//Wo shi ge da SHA BI
#include<bits/stdc++.h>
#define g() getchar()
#define il inline
#define ull unsigned long long
#define eps 1e-10
#define ll long long
#define pa pair<int, int>
#define for_1(i, n) for(int i = 1; i <= (n); ++i)
#define for_0(i, n) for(int i = 0; i < (n); ++i)
#define for_xy(i, x, y) for(int i = (x); i <= (y); ++i)
#define for_yx(i, y, x) for(int i = (y); i >= (x); --i)
#define for_edge(i, x) for(int i = head[x]; i; i = nxt[i])
#define int long long
#define DB double
#define ls (p<<1)
#define rs (p<<1|1)
#define m_p make_pair
#define fi first
#define se second
using namespace std;
const int N = 1e6 + 10, INF = 0x7f7f7f7f, mod = 1e9 + 7, M = 6000;
il int qpow(int x, int k) {int ans = 1; while(k) {if(k & 1) ans = ans * x % mod; x = x * x % mod; k >>= 1; } return ans; }
il int Add(int x, int y) {return (x += y) %= mod;}
il int Del(int x, int y) {return (x = x - y + mod) % mod;}
il int Mul(int x, int y) {return x * y % mod;}
inline int re() {
    int x = 0, p = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0') {if(ch == '-') p = -1; ch = getchar();}
    while(ch <= '9' and ch >= '0') {x = (x << 3) + (x << 1) + (ch ^ 48); ch = getchar();}
    return x * p;
}

int jc[N], inv[N], f[2][6020][2]; 
char s[5][N]; 
int n, sum, now; 
il int C(int x, int y) {if(y > x) return 0; if(x == y || y == 0) return 1; return jc[x] * inv[y] % mod * inv[x - y] % mod; }

signed main() { 
    // freopen("game.in","r",stdin); freopen("game.out","w",stdout); 
    jc[0] = jc[1] = 1, inv[0] = inv[1] = 1; 
    for_1(i, M) jc[i] = jc[i - 1] * i % mod; inv[M] = qpow(jc[M], mod - 2); for(int i = M - 1; i >= 1; --i) inv[i] = (inv[i + 1] * (i + 1) % mod) ; 
    n = re(); for_0(i, 3) scanf("%s", s[i] + 1); 
    for_1(i, n) { 
        if(s[0][i] == 'x' and (i==1 || i==n || s[0][i + 1] == 'x')) {cout<<0<<endl; return 0; }
        if(s[2][i] == 'x' and (i==1 || i==n || s[2][i + 1] == 'x')) {cout<<0<<endl; return 0; }
    }//判断无解的情况
    if(s[1][1] == 'x') f[0][sum = 1][0] = 1; else f[0][0][0] = 1; 
    for(int i = 2; i <= n; ++i) {
        now ^= 1; int pre = now ^ 1; memset(f[now], 0, sizeof(f[now])); 
        int d = (s[0][i] == 'x') + (s[2][i] == 'x'); 
        if(s[1][i] == 'o') {
            sum += d; f[now][0][0] = (f[pre][sum - d][1] + f[pre][sum - d][0]) % mod * C(sum, d) % mod * jc[d] % mod; //新的联通快
            for(int j = 1; j <= sum; ++j) f[now][j][0] = f[now][0][0]; continue; 
        }
        sum += (d + 1); 
        for(int j = 1; j <= sum; ++j) {
            f[now][j][0] = (f[now][j][0] + ((f[pre][sum - d - 1][1] - f[pre][max(j - d - 1, 0LL)][1] + mod) * C(j - 1, d) % mod * jc[d] % mod)) % mod; //当前选的是竖着的,前面的是横着的,那么必须要比上一位更先选才行
            f[now][j][0] = (f[now][j][0] + (f[pre][sum - d - 1][0] * C(j - 1, d) % mod * jc[d] % mod)) % mod;//当前是选的竖着的,前面也是竖着的,所以可以插到前面任意一个
            if(i < n) { 
                if(d >= 1) f[now][j][1] = (f[now][j][1] + f[pre][min(sum - d - 1, j - 1)][0] * C(sum - j, d) % mod * jc[d] % mod) % mod;//当前选的是横着的,那么前面竖着的必须要选上才行
                if(d >= 2) f[now][j][1] = (f[now][j][1] + f[pre][max(0LL, j - 2)][0] * (sum - j) % mod * (j - 1) % mod * 2 % mod) % mod;//这里就是强制去选了,由于优先级的问题,只统计一次,这里是前面选上一部分后面选下一部分,两者颠倒
            }
        }
        for_1(j, sum) { f[now][j][0] = (f[now][j][0] + f[now][j - 1][0]) % mod; f[now][j][1] = (f[now][j][1] + f[now][j - 1][1]) % mod; }
    }
    cout<<f[now][sum][0]<<endl; 
}
/*
3
oxo
xxo
oxo
*/
posted @ 2022-04-07 07:41  Zwaire  阅读(103)  评论(0)    收藏  举报