【LOJ】#2731. 「JOISC 2016 Day 1」棋盘游戏-DP

博客详细解析了LOJ2731题目,即「JOISC 2016 Day 1」中的棋盘游戏问题。通过动态规划方法解决,强调四个顶点必须为'o',第一行和第三行不允许有连续的'x'。题解指出第一行和第二行的选择相互独立,重点在于处理中间行的顺序。博主介绍了状态转移方程,使用前缀和优化,时间复杂度为O(n^2)。在实现过程中注意避免状态重复,并指出代码实现中的难点和需要注意的细节。

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

传送门:loj2731


题解

根据题目要求可以发现:

  • 四个顶点处必须是’o’
  • 第一行和第三行不存在连续的‘x’

故所有第一行第二行的’x’的选择顺序是互不干扰的,只需要考虑中间一行的顺序关系:

d p [ i ] [ j ] dp[i][j] dp[i][j]表示处理到第 i i i列,且第 i i i列是在第 j j j次操作选择了第二行格子的方案数,显然前 j j j列的操作数是固定的,向后转移相当于把这一列的操作插入到前面的所有操作中。

i i i列中间的格子可选当且仅当前 j − 1 j-1 j1个操作中 它上下/左右 被选,所以状态中还需要加一维限制它的后一个操作,即 d p [ i ] [ j ] [ 0 / 1 ] dp[i][j][0/1] dp[i][j][0/1]分别表示第 i i i列在第 j j j次操作中被选且是由上下/左右被选转移而来的方案数。

前缀和优化转移,时间复杂度 O ( n 2 ) O(n^2) O(n2)

注意:

  • 为避免算重(上下/左右均在第 j j j次前被选),设上下转移优先级高于左右转移,即 d p [ i ] [ j ] [ 1 ] dp[i][j][1] dp[i][j][1]的方案中强制存在至少一个上下格子在第 j j j次操作后被选。
  • 若第 i i i列中间一行为’o’,将所有状态转移到 f [ i ] [ 0 ] [ 0 ] f[i][0][0] f[i][0][0]即可。
  • 注意操作数 > n >n >n,空间至少要开两倍

代码

细节较多,调一年系列
值得一写

#include<bits/stdc++.h>
#define rep(i,j,k) for(i=j;i<=k;++i)
using namespace std;
typedef long long ll;
typedef double db;
const int N=2005,M=N*3,mod=1e9+7;

int n,f[2][M][2],sum,frc[M],nv[M];
char s[3][N];

inline void ad(int &x,int y){x+=y;if(x>=mod) x-=mod;}
inline int dc(int x,int y){x-=y;return x<0?x+mod:x;}

inline int C(int x,int y)
{
	if(y>x) return 0;
	if(x==y || y==0) return 1;
	return (ll)frc[x]*nv[y]%mod*(ll)nv[x-y]%mod;
}

int main(){
	int i,j,k,ss,d,x,t,pr=0;
	frc[0]=frc[1]=nv[0]=nv[1]=1;
	rep(i,2,6000)
	  frc[i]=(ll)frc[i-1]*i%mod,nv[i]=(ll)(mod-mod/i)*nv[mod%i]%mod;
	rep(i,2,6000) nv[i]=(ll)nv[i]*nv[i-1]%mod;
	scanf("%d",&n);rep(i,0,2) scanf("%s",s[i]+1);
	rep(i,1,n){
		if(s[0][i]=='x' && (i==1 || i==n || s[0][i+1]=='x')) {puts("0");return 0;}
		if(s[2][i]=='x' && (i==1 || i==n || s[2][i+1]=='x')) {puts("0");return 0;}
	}
	if(s[1][1]=='x') f[0][sum=1][0]=1;
	else f[0][0][0]=1;
	rep(i,2,n){
		pr^=1;memset(f[pr],0,sizeof(f[pr]));
		d=(s[0][i]=='x')+(s[2][i]=='x');
		if(s[1][i]=='o'){
			sum+=d;
			f[pr][0][0]=(f[pr^1][sum-d][1]+f[pr^1][sum-d][0])%mod*(ll)C(sum,d)*frc[d]%mod;
			rep(j,1,sum) f[pr][j][0]=f[pr][0][0];
			continue;
		}
		sum+=(d+1);
		rep(j,1,sum){
			ad(f[pr][j][0],(ll)dc(f[pr^1][sum-d-1][1],f[pr^1][max(j-d-1,0)][1])*C(j-1,d)%mod*(ll)frc[d]%mod);
		    ad(f[pr][j][0],(ll)f[pr^1][sum-d-1][0]*C(j-1,d)%mod*(ll)frc[d]%mod);
			if(i<n){
		    	if(d>=1) ad(f[pr][j][1],(ll)f[pr^1][min(sum-d-1,j-1)][0]*C(sum-j,d)*frc[d]%mod);
		    	if(d>1) ad(f[pr][j][1],(ll)f[pr^1][max(0,j-2)][0]*(sum-j)%mod*(ll)(j-1)*2%mod);
		    }
		}
		rep(j,1,sum)
		  ad(f[pr][j][0],f[pr][j-1][0]),
		  ad(f[pr][j][1],f[pr][j-1][1]);
	}
	printf("%d",f[pr][sum][0]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值