题目描述
这次小可可想解决的难题和中国象棋有关,在一个N行M列的棋盘上,让你放若干个炮(可以是0个),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法。大家肯定很清楚,在中国象棋中炮的行走方式是:一个炮攻击到另一个炮,当且仅当它们在同一行或同一列中,且它们之间恰好 有一个棋子。你也来和小可可一起锻炼一下思维吧!
输入输出格式
输入格式: 一行包含两个整数N,M,之间由一个空格隔开。
总共的方案数,由于该值可能很大,只需给出方案数模9999973的结果。
输入输出样例
1 3
7
说明
样例说明
除了3个格子里都塞满了炮以外,其它方案都是可行的,所以一共有2*2*2-1=7种方案。
数据范围
100%的数据中N和M均不超过100
50%的数据中N和M至少有一个数不超过8
30%的数据中N和M均不超过6
解题报告
很显然,若要炮不能互相攻击,那么每行每列的炮不能超过2个。
动态规划的话,可以用DP[i][k]表示前i行状态为k(k为一个三进制数,表示每列放了多少个)的方案数,转移的话只要枚举每行放哪一个或哪两个位置放就行了,这样的话时间复杂度为O(m^2*n*3^m),肯定过不了,原因就是状态太复杂。
那怎么优化状态呢?
可以发现我们其实不需要知道具体每一列的状态,只要知道个数为0/1/2的各有几列就行了,因为只要0/1/2的列数相同,那么这些状态虽然排布不同,但本质上是一样的,直接乘法就可以得到。(另一种理解就是每次放完之后把所有炮个数相同的列都移到一起,例如把02101变成00112本质上是一样的)
这样,我们就可以用Opt[i][j][k]表示前i行有j列不放有k列放1个的方案数(放两个的就是m-k-j)。
现在来考虑状态的转移:
Opt[i][j][k]+=Opt[i-1][j][k]; //不放的情况
不放的话状态不变,所以也没有限制条件。
Opt[i][j][k]+=Opt[i-1][j+1][k-1]*(j+1); //放一个在原来没有的上
原来没有的少了一列,1个的多了一列,原来没有的都可以放(即(j+1)种放法),要满足当前1个的至少一列(即k>0)
Opt[i][j][k]+=Opt[i-1][j][k+1]*(k+1); //放一个在原来有1个的上
原来1个的少了一列,2个的多了一列,原来1个的都可以放(即(k+1)种放法)要满足当前2个的至少一列(即m-k-j>0)
Opt[i][j][k]+=Opt[i-1][j+1][k]*(j+1)*k; //一个放在原来没有的,另一个放在原来有一个的
原来没有的少了一列,1个的多了一列又少了1列,有(j+1)*k种放法,要满足k>0&&(m-k-j>0)
Opt[i][j][k]+=Opt[i-1][j+2][k-2]*(j+2)*(j+1)/2; //两个都放在原来没有的上
原来没有的少了两列,1个的多了两列,有(j+1)*(j+2)种放法,要满足当前1个的至少两列(即k>1)
Opt[i][j][k]+=Opt[i-1][j][k+2]*(k+2)*(k+1)/2; //两个都放在原来有一个的上
基本同上。
初始化时一定是Opt[0][m][0]=1!!!
源代码
#include<bits/stdc++.h>
using namespace std;
int n,m,i,j,k,Ans=0;
long long Opt[101][300][300]; //Opt[i][j][k]表示前i行有j列没放有k列放了1个的方案数
int main(){
cin>>n>>m;
Opt[0][m][0]=1; //初始化时一个都不放的情况赋值为1(不能误写成Opt[0][0][0])
for(i=1;i<=n;++i)
for(j=0;j<=m;++j)
for(k=0;k<=m-j;++k){
Opt[i][j][k]+=Opt[i-1][j][k]; //不放的情况
if(k)
Opt[i][j][k]+=Opt[i-1][j+1][k-1]*(j+1); //放一个在原来没有的上
if(m-k-j)
Opt[i][j][k]+=Opt[i-1][j][k+1]*(k+1); //放一个在原来有1个的上
if(k&&(m-k-j))
Opt[i][j][k]+=Opt[i-1][j+1][k]*(j+1)*k; //一个放在原来没有的,另一个放在原来有一个的
if(k>=2)
Opt[i][j][k]+=Opt[i-1][j+2][k-2]*(j+2)*(j+1)/2; //两个都放在原来没有的上
if(m-j-k>=2)
Opt[i][j][k]+=Opt[i-1][j][k+2]*(k+2)*(k+1)/2; //两个都放在原来有一个的上
Opt[i][j][k]%=9999973;
}
for(j=0;j<=m;++j)
for(k=0;k<=m-j;++k)
Ans=(Ans+Opt[n][j][k])%9999973;
cout<<Ans<<endl;
}
解题小结
动态规划问题中,若遇到状态过于复杂的情况,要考虑状态的简化,尽量将同一类的状态合并,从而简化计算。