[loj2731]「JOISC 2016 Day 1」棋盘游戏

本文解析了一个在3xn棋盘上进行的游戏算法,目标是计算所有可能的棋子放置顺序。文章深入探讨了算法思路,包括无解条件判断、状态划分、动态规划应用及代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

有一个3 × n的棋盘,你在上面玩游戏。开始时,棋盘有一些格子上已经摆上了棋子,剩下的格子都是空的。每次你可以选择一个空的格子摆上棋子,这个格子必须满足以下两个条件之一:

  1. 这个格子上下两格都有棋子;
  2. 这个格子左右两格都有棋子。
    你想知道有多少种不同的摆满棋盘的摆放顺序。
思路

首先判断无解,如果四个角没有棋子或者一三行出现了连续两个空格子,那么就无解.

然后发现整个图变成了这个样子:

oxoxoxoxo
xxxxoxxxo
oxoxoxoxo

我们发现一三行的格子任意时刻都可以放上棋子,只有中间一行的棋子我们需要考虑先后顺序.

考虑根据中间那一行的连通性来划分联通块,每一个块之间可以独立计算,不互相影响.

然后对于每一个块,我们考虑对最后的摆放的位置序列进行dp,每次将一个列的元素添加到序列中,不难发现对于每一列中间的元素,我们有两种方式将它加入进去:

  1. 上下都有棋子
  2. 左右都有棋子

为了不算重,我们将两者都满足的情况算成第一种,对于第一种情况的转移,不难发现和旁边两列摆放的位置没有任意影响,但是对于第二种,我们要求旁边两列的棋子均摆放在这一列的前面.

于是设状态f[i][j][0/1]表示第i列的中间那个棋子是第j个加进序列,且是通过第1/2种方案加进去的.

直接转移是\(n^3\)的,可以用前缀和优化至\(n^2\).

/*=======================================
 * Author : ylsoi
 * Time : 2019.2.26
 * Problem : game
 * E-mail : ylsoi@foxmail.com
 * ====================================*/
#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
#define debug(x) cout<<#x<<"="<<x<<" "
#define fi first
#define se second
#define mk make_pair
#define pb push_back
typedef long long ll;

using namespace std;

void File(){
    freopen("game.in","r",stdin);
    freopen("game.out","w",stdout);
}

template<typename T>void read(T &_){
    _=0; T f=1; char c=getchar();
    for(;!isdigit(c);c=getchar())if(c=='-')f=-1;
    for(;isdigit(c);c=getchar())_=(_<<1)+(_<<3)+(c^'0');
    _*=f;
}

const int maxn=2000+10;
const int mod=1e9+7;
int n,f[maxn][maxn*3][2];
char s[4][maxn];
int fac[maxn*3],ifac[maxn*3];

void inc(int &x,int y){(x+=y)>=mod ? x-=mod : x<0 ? x+=mod : 0;}

int qpow(int x,int y){
    int ret=1; x%=mod;
    while(y){
        if(y&1)ret=1ll*ret*x%mod;
        x=1ll*x*x%mod;
        y>>=1;
    }
    return ret;
}

void math_init(){
    fac[0]=1;
    REP(i,1,6000)fac[i]=1ll*fac[i-1]*i%mod;
    ifac[6000]=qpow(fac[6000],mod-2);
    DREP(i,5999,0)ifac[i]=1ll*ifac[i+1]*(i+1)%mod;
}

int C(int x,int y){
    if(x<0 || y<0 || x<y)return 0;
    return 1ll*fac[x]*ifac[y]%mod*ifac[x-y]%mod;
}

int main(){
    File();
    read(n);
    REP(i,1,3)scanf("%s",s[i]+1);

    REP(i,1,n){
        if(s[1][i]=='x' && (i==1 || i==n || s[1][i-1]=='x' || s[1][i+1]=='x'))puts("0"),exit(0);
        if(s[3][i]=='x' && (i==1 || i==n || s[3][i-1]=='x' || s[3][i+1]=='x'))puts("0"),exit(0);
    }

    math_init();


    int sum=0,now=0,ans=1;

    if(s[2][1]=='x'){
        f[1][1][0]=1,now=1;
        if(n==1 || s[2][2]=='o')sum=1,now=0;
    }

    REP(i,2,n){
        int cur=(s[1][i]=='x')+(s[3][i]=='x');

        if(s[2][i]=='o'){
            sum+=cur;
            ans=1ll*ans*fac[cur]%mod*C(sum,cur)%mod;
            continue;
        }
        ++cur;
        now+=cur;

        REP(j,1,now){
            int per=fac[cur-1];
            if(now==cur){
                if(j==cur)f[i][j][0]=per;
                if(i<n && j!=cur)f[i][j][1]=per;
            }
            else{
                inc(f[i][j][0],1ll*f[i-1][now-cur][0]*per%mod*C(j-1,cur-1)%mod);
                inc(f[i][j][0],1ll*(f[i-1][now-cur][1]-f[i-1][max(j-cur,0)][1])*per%mod*C(j-1,cur-1)%mod);
                if(i<n){
                    if(cur==2)inc(f[i][j][1],1ll*f[i-1][min(now-cur,j-1)][0]*per%mod*(now-j)%mod);
                    if(cur==3){
                        inc(f[i][j][1],1ll*f[i-1][min(now-cur,j-1)][0]*per%mod*C(now-j,2)%mod);
                        inc(f[i][j][1],1ll*f[i-1][max(j-2,0)][0]*per%mod*(j-1)%mod*(now-j)%mod);
                    }
                }
            }
        }

        if(i==n || s[2][i+1]=='o'){
            sum+=now;
            int ts=0;
            REP(j,1,now)inc(ts,(f[i][j][0]+f[i][j][1]*(i<n))%mod);
            ans=1ll*ans*ts%mod*C(sum,now)%mod;
            now=0;
        }

        REP(j,1,now){
            inc(f[i][j][0],f[i][j-1][0]);
            inc(f[i][j][1],f[i][j-1][1]);
        }

    }

    printf("%d\n",ans);

    return 0;
}

转载于:https://www.cnblogs.com/ylsoi/p/10444019.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值