题目描述
这次小可可想解决的难题和中国象棋有关,在一个N行M列的棋盘上,让你放若干个炮(可以是0个),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法。大家肯定很清楚,在中国象棋中炮的行走方式是:一个炮攻击到另一个炮,当且仅当它们在同一行或同一列中,且它们之间恰好 有一个棋子。你也来和小可可一起锻炼一下思维吧!
输入格式:
一行包含两个整数N,M,之间由一个空格隔开。
输出格式:
总共的方案数,由于该值可能很大,只需给出方案数模9999973的结果。
数据范围
100%的数据中N和M均不超过100
50%的数据中N和M至少有一个数不超过8
30%的数据中N和M均不超过6
显然,同一行里我们最多放2个棋子。
限制我们放棋子的条件是纵列。
我的初步想法是,状压把当前行表示出来,记录前面的列放了棋子数,合法转移。
先不说这样一定会T。。代码实现就很难。要用到三进制状压DP。
void init()//预处理三进制状态每一位的值
{
state[0]=1;
for(int i=1;i<=10;i++) state[i]=state[i-1]*3;
for(int i=0;i<=state[10];i++){
int tmp=i;
for(int j=0;j<=10;j++){
vis[i][j]=tmp%3;
tmp/=3;
}
}
这样就很麻烦。。而且n,m是100。根本就没法做。。
然而对于任何一个状态,我们并不关心他的具体内容,只关心方案数。我们可不可以处理出转移的方案数,而不表示具体状态?
综上:我们需要一种状态,既可以体现出纵列的约束,又不表示具体状态使得转移方便。
所以f【i】【j】【k】表示前i行,有j列放了一个棋子,有k列放了2个棋子。
显然,初始状态f【0】【0】【0】。
对于每一行,我们的操作有:
1.不放 直接转移
2.放一个在无棋子的列。(有n列状态数则*n)
3.放在一个有棋子的列。(有n列状态数*n)
4.放2个,都在无棋子的列(C n 2)
5.放2个,都在有1个棋子的列(C n 2)
6.放2个,一个在无棋子的列,一个在有棋子的列(乘法原理 n*m)。
然后转移最后求一下 Σf【n】【j】【k】就好了。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod=9999973;
const ll MAXN=105;
ll n,m;
inline ll CX2(int X){
return X*(X-1)/2;
}
ll dp[MAXN][MAXN][MAXN];//f[i][j][k]表示 前i行,有j列放了1个,有k列放了2个。
//j+k+空=m
int main(){
memset(dp,0,sizeof(dp));
scanf("%lld%lld",&n,&m);
dp[0][0][0]=1;
for(int i=0;i<n;i++)
for(int j=0;j<=m;j++)
for(int k=0;k+j<=m;k++){
if(dp[i][j][k]){//可以略省时间
(dp[i+1][j][k]+=dp[i][j][k])%=mod;//不放
if(j+k+1<=m*2&&m-j-k>0)
(dp[i+1][j+1][k]+=(dp[i][j][k]*(m-j-k)))%=mod;//放一个放在空处
if(j+k+1<=m*2&&j>=1)
(dp[i+1][j-1][k+1]+=(dp[i][j][k]*j))%=mod;//放一个在1处
if(j+k+2<=m*2&&m-j-k>1)
(dp[i+1][j+2][k]+=(dp[i][j][k]*CX2(m-j-k)))%=mod;//2个都在0处放且不是一个地方
if(j+k+2<=m*2&&j>=2)
(dp[i+1][j-2][k+2]+=(dp[i][j][k]*CX2(j)))%=mod;//都在1处放
if(j+k+2<=m*2&&m-j-k>0&&j>=1)
(dp[i+1][j][k+1]+=(dp[i][j][k]*((m-j-k)*j)))%=mod;//一个放0,一个放1.
}
}
ll ans=0;
for(int j=0;j<=m;j++)
for(int k=0;k+j<=m;k++){
(ans+=dp[n][j][k])%=mod;
}
printf("%lld\n",ans);
return 0;
}