【51nod1327】棋盘游戏

本文探讨了一种动态规划(DP)方法,用于解决在一个n×m的棋盘上放置棋子的问题,确保每行指定位置有棋子,且每列最多一个棋子,最终求解所有合法放置方案的数量。

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

题意

n×m的棋盘,放棋子,要求第i行左边lil_ili个格子和右边rir_iri个格子各恰好有一个棋子,且每一列最多只能有1个棋子,问方案数,对109+710^9+7109+7取模
n≤50,m≤200n\leq50,m\leq200n50,m200

Solution

这是一道DP好题。
太菜了以至于我想了半天连状态都没想到。
考虑到每一列最多放一个,那么考虑在列上做文章,设f[i][]f[i][]f[i][]表示做到了第i列……
然后怎么办呢,对于一列,假如一些行左区间到此为止了,那么就一定要将这些行放完,将这些行安排之前没有被放过的列;否则先把它们咕咕了。如果一些行的右区间从这一列开始,那么没有处理完的行就会多出几个。其次你也可以选择填在中间没有覆盖的地方。
如果这么考虑的话,状态就很好想了(可是就是没有这么考虑)
f[i][j][k]f[i][j][k]f[i][j][k]表示做到了第i列,在之前有j列没有被放过棋子,有k行已经到了右区间。
li,ri,midil_i,r_i,mid_ili,ri,midi分别为左区间右端点为i、右区间左端点为i、i列没有区间覆盖住的行数
每一列的转移要添加ri+1r_{i+1}ri+1的右区间放置机会,要将l[i+1]l[i+1]l[i+1]全部搞定。
初状态为f[0][0][0]=1f[0][0][0]=1f[0][0][0]=1
考虑三种转移:

  1. 不放在中间空着的,也不放在右区间,那么就要将li+1l_{i+1}li+1行安排前面没放棋子的那j列f[i+1][j+1−li+1][k+ri+1]+=f[i][j][k]∗Aj+1li+1f[i+1][j+1-l_{i+1}][k+r_{i+1}]+=f[i][j][k]*A_{j+1}^{l_i+1}f[i+1][j+1li+1][k+ri+1]+=f[i][j][k]Aj+1li+1
  2. 放在右区间f[i+1][j−li+1][k+ri+1−1]+=f[i][j][k]∗Ajli+1∗(k+ri+1)f[i+1][j-l_{i+1}][k+r_{i+1}-1]+=f[i][j][k]*A_j^{l_{i+1}}*(k+r_{i+1})f[i+1][jli+1][k+ri+11]+=f[i][j][k]Ajli+1(k+ri+1)
  3. 放在中间空着的位置f[i+1][j−li+1][k+ri+1]+=f[i][j][k]∗Ajli+1∗midi+1f[i+1][j-l_{i+1}][k+r_{i+1}]+=f[i][j][k]*A_j^{l_{i+1}}*mid_{i+1}f[i+1][jli+1][k+ri+1]+=f[i][j][k]Ajli+1midi+1
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(int i(a),_E_(b);i<=_E_;++i)
#define REP(i,a,b) for(int i(a),_E_(b);i<_E_;++i)
#define fd(i,a,b) for(int i(a),_E_(b);i>=_E_;--i)
#define ll long long
#define mo 1000000007
#define N 52
#define M 210

using namespace std;

int f[M][M][N],l[M],r[M],mid[M],n,m,p[M][M];

int main(){
	scanf("%d %d",&n,&m);
	fo(i,0,m){
		p[i][0]=1;fo(j,1,i)p[i][j]=1ll*p[i][j-1]*(i-j+1)%mo;
	}
	fo(i,1,n){
		int L,R;scanf("%d %d",&L,&R);
		++l[L];++r[m-R+1];++mid[L+1];--mid[m-R+1];
	}
	fo(i,2,m)mid[i]+=mid[i-1];
	f[0][0][0]=1;
	REP(i,0,m)fo(j,0,i)fo(k,0,n)if(f[i][j][k]){
		if(j+1>=l[i+1])(f[i+1][j+1-l[i+1]][k+r[i+1]]+=1ll*p[j+1][l[i+1]]*f[i][j][k]%mo)%=mo;
		if(j>=l[i+1]&&k+r[i+1])(f[i+1][j-l[i+1]][k-1+r[i+1]]+=1ll*p[j][l[i+1]]*(k+r[i+1])%mo*f[i][j][k]%mo)%=mo;
		if(j>=l[i+1])(f[i+1][j-l[i+1]][k+r[i+1]]+=1ll*p[j][l[i+1]]*mid[i+1]%mo*f[i][j][k]%mo)%=mo;
	}int ans=0;fo(i,0,m)ans=(ans+f[m][i][0])%mo;
	printf("%d",ans);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值