题目描述
这次小可可想解决的难题和中国象棋有关,在一个N行M列的棋盘上,让你放若干个炮(可以是0个),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法。大家肯定很清楚,在中国象棋中炮的行走方式是:一个炮攻击到另一个炮,当且仅当它们在同一行或同一列中,且它们之间恰好 有一个棋子。你也来和小可可一起锻炼一下思维吧!
输入输出格式
输入格式
一行包含两个整数N,M,之间由一个空格隔开。
输出格式
总共的方案数,由于该值可能很大,只需给出方案数模9999973的结果。
输入输出样例
输入样例#1
1 3
输出样例#1
7
解题思路
又是一道状态转移方程的总面积十分庞大的DP题
我们都玩过中国象棋,知道只要两个炮之间有且只有一个棋子时就能够互相吃掉,那么为了满足棋盘上的炮两两不相冲突,就必须满足每行每列最多仅能有两个炮,想到这,我们便可维护这样的状态变量:
f [ i ] [ j ] [ k ] 表示第 i 行,其中有 j 列放了一个炮,有 k 列放了两个炮,此时的方案数,为了方便实现代码,我们每次将第 i 行的状态转移到第 i + 1 行,通过推理,我们得出有以下几种情况可转移:
- 在第 i + 1 行不放, f [ i + 1 ] [ j ] [ k ] += f [ i ] [ j ] [ k ] ;
- 在第 i + 1 行放一个棋子,又分以下几种情况:
- 可以在没有放过棋子的一列放, f [ i + 1 ] [ j + 1 ] [ k ] += f [ i ] [ j ] [ k ] ;
- 可以在放了一个棋子的某一列放, f [ i + 1 ] [ j - 1 ] [ k + 1 ] +=
f [ i ] [ j ] [ k ] ;
- 在第 i + 1 行放了两个棋子,又可以分为以下几种情况:
- 两个棋子都分别放在没有放过棋子的一列,
f [ i + 1 ] [ j + 2 ] [ k ] += f [ i ] [ j ] [ k ] ; - 两个棋子分别放在没有棋子的一列和有一个棋子的一列,
f [ i + 1 ] [ j ] [ k + 1 ] += f [ i ] [ j ] [ k ] ; - 两个棋子都分别放在放过一个棋子的一列,
f [ i + 1 ] [ j - 2 ] [ k + 2 ] += f [ i ] [ j ] [ k ] ;
最后再把总共 n 行的每种情况加起来就是最终的答案,需要注意的就是在状态转移的时候对于每种转移情况的判断以及统计答案时的取模,其他细节看下面的代码注释
- 两个棋子都分别放在没有放过棋子的一列,
C++代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define rr register
using namespace std;
typedef long long LL;
const int maxn=100+10;
const int mod=9999973;
int n,m;
LL f[maxn][maxn][maxn];
LL ans=0;
inline LL read(){//珂朵莉版快读~~~
LL chtholly=0,william=1;
char c=getchar();
while(c<'0' || c>'9'){
if(c=='-')
william=-1;
c=getchar();
}
while(c<='9' && c>='0'){
chtholly=chtholly*10+(LL)(c-'0');
c=getchar();
}
return chtholly*william;
}
inline LL c(LL x){return x*(x-1)/2%mod;}
int main(){
n=read(),m=read();
f[0][0][0]=1;//初始化,第0行0列放一个棋子0行放两个棋子只有一种方案
for(rr int i=0;i<n;i++)for(rr int j=0;j<=m;j++)for(rr int k=0;k+j<=m;k++)
if(f[i][j][k]){
f[i+1][j][k]=(f[i+1][j][k]+f[i][j][k])%mod;
//不放
if(m-j-k>=1)f[i+1][j+1][k]=(f[i+1][j+1][k]+f[i][j][k]*(m-j-k))%mod;
//放一个,在没放过一列
if(j>=1)f[i+1][j-1][k+1]=(f[i+1][j-1][k+1]+f[i][j][k]*j)%mod;
//放一个,在放过一个的一列
if(m-j-k>=2)f[i+1][j+2][k]=(f[i+1][j+2][k]+f[i][j][k]*c(m-j-k))%mod;
//放两个,分别放在没放过的两列
if(j>=1 && m-j-k>=1)f[i+1][j][k+1]=(f[i+1][j][k+1]+f[i][j][k]*(m-j-k)*j)%mod;
//放两个,一个放在没放过的,一个放在放过一个的
if(j>=2)f[i+1][j-2][k+2]=(f[i+1][j-2][k+2]+f[i][j][k]*c(j))%mod;
//放两个,分别放在放过一个的两列
}
for(rr int i=0;i<=m;i++)for(int j=0;j+i<=m;j++)
ans=(ans+f[n][i][j])%mod;
//统计答案
printf("%lld\n",ans);
return 0;
}
网友评论