FZOJ186. 「2019冬令营提高组」简单题(动态规划)

题目大意:
有一个 3 × n 3×n 3×n的棋盘,你在上面玩游戏。开始时,棋盘有一些格子上已经摆上了棋子,剩下的格子都是空的。每次你可以选择一个空的格子摆上棋子,这个格子必须满足以下两个条件之一:
①这个格子上下两格都有棋子;
②这个格子左右两格都有棋子。
你想知道有多少种不同的摆满棋盘的摆放顺序。
n ≤ 2000 n\le 2000 n2000


显然如果四个角落有空肯定不合法,第一排和第三排有连续的空不合法,其它必然合法

如果把棋盘看成四联通显然每个联通快互不影响,考虑分开计算后用组合数合起来

考虑怎么算一个联通块的答案

f [ i ] [ j ] [ 0 / 1 ] f[i][j][0/1] f[i][j][0/1]表示前 i i i 列,第 i i i 列中间的空格在操作序列中位于 j j j ,下一个是否要在这一列前面

转移的时候注意一些避免重复的 t r i c k trick trick ,前后缀优化即可

#include<bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i = j;i <= k;++i)
#define repp(i,j,k) for(int i = j;i >= k;--i)
#define ll long long
const int p = 1e9+7;
int n,g[2010],sum[2010];
int f[2010][6010][2],fac[6010],inv[6010],ans;
bool a[2010],b[2010],c[2010];
char s[2010];
inline int mul(int a,int b){return 1ll*a*b%p;}
inline void add(int &a,int b){a += b;if(a>=p) a-= p;}
inline int ksm(int a,int x){int now = 1;for(;x;x>>=1,a=1ll*a*a%p) if(x&1) now = 1ll*now*a%p;return now;}
inline int A(int x,int y){return (y<0||y>x)?0:mul(fac[x],inv[x-y]);}
void solve(){
	for(int i = 1,j;i <= n;i = j+1){
		j = i; if(!b[i]) continue;
		while(b[j])j++;j--;
		rep(k,1,g[i]) f[i][k][1] = mul(k,fac[g[i]-1]); f[i][1][0] = fac[g[i]-1];
		rep(k,i+1,j){
			int tot = sum[k]-sum[i-1];
			rep(t,1,tot){
				add(f[k][t][1],mul(f[k-1][t-1][1],A(tot-t,g[k]-1)));
				add(f[k][t][0],mul(f[k-1][t-1][1],A(tot-t,g[k]-1)));
				add(f[k][t][0],mul(f[k-1][t][0],A(tot-t,g[k]-1)));
				if(g[k] == 1) add(f[k][t][1],f[k-1][t][0]);
				if(g[k] == 2) add(f[k][t][1],mul(f[k-1][t][0],tot-t)),
				              add(f[k][t][1],mul(f[k-1][t-1][0],t-1));
				if(g[k] == 3) add(f[k][t][1],mul(f[k-1][t][0],A(tot-t,2))), 
				              add(f[k][t][1],mul(f[k-1][t-2][0],A(t-1,2))), 
				              add(f[k][t][1],mul(f[k-1][t-1][0],mul(2*t-2,tot-t)));
			}
			rep(t,1,tot) add(f[k][t][1],f[k][t-1][1]);
			repp(t,tot,1) add(f[k][t][0],f[k][t+1][0]);
		}
		ans = mul(ans,inv[sum[j]-sum[i-1]]); ans = mul(ans,f[j][sum[j]-sum[i-1]][1]);
	}
	printf("%d\n",ans);
}
int main(){
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	scanf("%d",&n); scanf("%s",s+1); rep(i,1,n) a[i] = s[i] == 'x';
	scanf("%s",s+1); rep(i,1,n) b[i] = s[i] == 'x'; scanf("%s",s+1); rep(i,1,n) c[i] = s[i] == 'x';
	if(a[1] || a[n] || c[1] || c[n]) {printf("0\n");return 0;}
	rep(i,2,n-2) if(a[i] && a[i+1]) {printf("0\n");return 0;} rep(i,2,n-2) if(c[i] && c[i+1]) {printf("0\n");return 0;}
	rep(i,1,n) g[i] = a[i] + b[i] + c[i],sum[i] = sum[i-1] + g[i];
    fac[0] = 1;rep(i,1,3*n) fac[i] = 1ll*fac[i-1]*i%p; inv[3*n] = ksm(fac[3*n],p-2); repp(i,3*n-1,0) inv[i] = 1ll*inv[i+1]*(i+1)%p;
	ans = fac[sum[n]];
	solve();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值