BZOJ1801 [AHOI2009] chess(dp/组合数学)

这篇博客讨论了在一个n行m列的棋盘上放置炮的组合方式,使得没有任何一个炮能攻击另一个炮。通过将棋盘视为二分图并使用记忆化搜索,博主给出了递推公式来解决这个问题。文章中还提供了C++代码实现来计算这种放置方法的数量,并对结果进行了模运算。
摘要由CSDN通过智能技术生成

题目

在n(n<=100)行m(m<=100)列的棋盘上,

放若干个炮,可以是0个,使得没有任何一个炮可以攻击另一个炮。

请问有多少种放置方法,答案对9999973取模。

一个炮要能攻击另一个炮,他们必须要处于同一行或者一列且他们之间有且仅有一个棋子。

思路来源

蔡老师

题解

不存在三个炮位于同一行或者同一列,即每一行最多有两个炮,每一列最多有两个炮。

于是可以考虑把棋盘考虑成一个二分图,左侧的点代表行,右侧的点代表列, 每个点的度数<=2

dfs(i,j,k)表示还需要考虑右边的[1,i]列时,左边还有j个还能放一个炮的行,k个还能放两个炮的行,

对于dfs(m,i,j)的每对(i,j),先从n行里分别选出i行和j行用来固定用途,然后记忆化搜索,

对于每一列来说,显然有六种情况:

①一个炮也不放

②放一个炮,从1里选

③放一个炮,从2里选

④放两个炮,1+1

⑤放两个炮,1+2

⑥放两个炮,2+2

m列最多能放2*m个炮,不合法的边界情况即j+2*k>2*m

注意到i的状态依赖i-1的状态,则反过来也可以写成线性递推的形式

代码

#include<bits/stdc++.h>
using namespace std;
const int N=105,mod=9999973;
int n,m,ans,fac[N],finv[N],dp[N][N][N];
int C(int n,int m){
	if(n<0 || m<0 || n<m)return 0;
	return 1ll*fac[n]*finv[m]%mod*finv[n-m]%mod;
}
void init(){
	fac[0]=finv[0]=fac[1]=finv[1]=1;
	for(int i=2;i<N;++i){
		fac[i]=1ll*fac[i-1]*i%mod;
		finv[i]=1ll*(mod-mod/i)*finv[mod%i]%mod;
	}
	for(int i=2;i<N;++i){
		finv[i]=1ll*finv[i-1]*finv[i]%mod;
	}
}
int dfs(int col,int one,int two){
	if(one<0 || two<0)return 0; 
	if(one+2*two>2*col)return 0;
	if(!col)return 1;
	int &ans=dp[col][one][two];
	if(~ans)return ans;
	ans=0;
	ans=(ans+dfs(col-1,one,two))%mod;//0
	ans=(ans+1ll*one*dfs(col-1,one-1,two)%mod)%mod;//1
	ans=(ans+1ll*two*dfs(col-1,one+1,two-1)%mod)%mod;//1
	ans=(ans+1ll*one*two%mod*dfs(col-1,one,two-1)%mod)%mod;//2
	ans=(ans+1ll*C(one,2)*dfs(col-1,one-2,two)%mod)%mod;//2
	ans=(ans+1ll*C(two,2)*dfs(col-1,one+2,two-2)%mod)%mod;//2
	return ans;
} 
int main(){
	init();
	memset(dp,-1,sizeof dp);
	scanf("%d%d",&n,&m);
	for(int i=0;i<=n;++i){//1
		for(int j=0;i+j<=n;++j){//2
			if(i+2*j<=2*m){
				ans=(ans+1ll*C(n,i+j)*C(i+j,j)%mod*dfs(m,i,j)%mod)%mod;
			}
		}
	}
	printf("%d\n",ans);
	return 0;
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值