题目
在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;
}