您的位置:首页 > 其它

动态规划/递推 - 中国象棋

2017-06-13 15:01 751 查看
中国象棋

总时间限制: 10000ms 单个测试点时间限制: 1000ms 内存限制: 65536kB

描述

在N行M列的棋盘上,放若干个炮可以是0个,使得没有任何一个炮可以攻击另一个炮。请问有多少种放置方法?中国象棋中炮的行走方式大家应该很清楚吧。

输入

一行包含两个整数N,M,中间用空格分开。

输出

输出所有的方案数,由于值比较大,输出其mod 9999973

样例输入

1 3

样例输出

7

提示

除了在3个格子中都放满炮的的情况外,其它的都可以.

  100%的数据中N,M不超过100

  50%的数据中,N,M至少有一个数不超过8

  30%的数据中,N,M均不超过6

  

来源

AHOI2009

这道题拿到手上,看了下数据规模,就已经知道了这道题要用动规。但是怎么动规呢?就让我们一步一步地推导吧。

动规的一大难点是定义状态,如果状态定义错了,更不要想找到什么状态转移方程了。好的状态不仅能让整个程序得出结果快,而且还好理解。然而,定义状态的基本前提还是 定义出来的状态能准确说明一种情况并且满足动规的两个原则。

对于这道题而言,首先我们肯定会想到把“已经下了好多棋”定义为一个状态。这个方向是正确的,但是不能盲目。首先,我们可以定义“在前i行中下棋”,这样就可以根据“在前i-1行中下棋”的结果来计算“在前i行中下棋”的结果了。至关重要的是,该怎么算。我们已经知道,每一行,每一列最多能下两枚棋,而在第i行的某一列能否下棋,其实是要取决于之前的每列下了多少枚棋的。因此,我们可以考虑这个状态:

long long f[i][j][k][l]; //下了i行棋,其中有j列上有1枚棋,有k列上有2枚棋,有l列上有0枚棋


看我1 2 0的顺序,你也应该知道了:状态l是可以被砍掉的。因为如果知道了有j列上下了一枚棋,有k列上下了两枚棋,那么没有下棋的列是可以算出来的,为m-j-k。这满足了“准确说明一种情况”的原则。因此新的状态为:

long ong f[i][j][k]; //下了i行棋,其中有j列上有1枚棋,有k列上有2枚棋


所以就可以考虑状态转移方程了。在根据放前i-1行的方案总数计算前i行的方案总数时在分情况时应遵循加法原理。

long long &cnt = f[i][j][k];
//当前行一个都不放
cnt=f[i-1][j][k] % mod;
//当前行放一个
cnt=(cnt + f[i-1][j-1][k] * (m-k-(j-1))) % mod;
cnt=(cnt + f[i-1][j+1][k-1] * (j+1)) % mod;
//当前行放两个
cnt=(cnt + f[i-1][j-2][k] * C(2, m-k-(j-2))) % mod;
cnt=(cnt + f[i-1][j-1+1][k-1] * (m-(k-1)-j) * j) % mod;
cnt=(cnt + f[i-1][j+2][k-2] * C(2, j+2)) % mod;


可以分6种情况:

1.若第i行一个都不放,就是这么多(见代码)

2.若第i行放1个

I.若都放在了之前一个都没有放的列上,则要用到
f[i-1][j-1][k]
,因为放之前有j-1列上有1个棋子。而能放的地方有
m-k-(j-1)
个(即放这一个之前一个都没有放的列数),所以根据乘法原理乘上一个
m-k-(j-1)


如果以上推导的思路懂了,后面的就能自己推导了。

II.放在了之前已经放了一个的列上。这样会使放了两个的列的列数加1,所以要用到
f[i-1][j+1][k-1]
,有
j+1
个可用位置。

3.若第i行放2个

I.都放在之前一个都没有放的列上。乘上的数为
C(2, m-k-(j-2))


II.一个放在之前没有放的列上,一个放在之前放了一个的列上。乘上的数为
(m-(k-1)-j) * (j-1+1)
。为什么要-1+1?因为一个放在没有放的列上会使放一个的列数+1,而另一个放在放一个的列上会使放一个的列数-1,相当于放一个的列数没有变。

III.两个都放在之前放了一个的列上。乘上的数为
C(2, j+2)


当然,这只是思路,还要加上一些条件,见完整参考代码。

参考代码

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <iostream>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
using std::cin;
using std::cout;
using std::endl;

const int maxn=105;
const int mod=9999973;
int n,m;
//i,j,k
//放置了前i行,其中j列放置了一个炮,k列放置了两个炮
//没有放置炮的列数为m-i-j
//初始状态f[0][0][0]=1
long long f[maxn][maxn][maxn];

long long C(int up, int down) //未处理溢出,因为这里的up只可能出现2。这个函数只是为了方便理解 。
{
long long ans=1;
int maxi=std::max(up,down-up);
for(int i=down; i>maxi; i--)
{
ans=ans*i;
}
for(int i=2; i<=down-maxi; i++)
{
ans/=i;
}
return ans%mod;
}

int main()
{
scanf("%d%d",&n,&m);
f[0][0][0]=1;
for(int i=1; i<=n; i++) //一行一行放
{
for(int j=0; j<=m; j++)
{
for(int k=0; k<=m; k++)
{
long long &cnt = f[i][j][k];
//当前行一个都不放
cnt=f[i-1][j][k] % mod;
//当前行放一个
if(j) cnt=(cnt + f[i-1][j-1][k] * (m-k-(j-1))) % mod;
if(k) cnt=(cnt + f[i-1][j+1][k-1] * (j+1)) % mod;
//当前行放两个
if(j>1) cnt=(cnt + f[i-1][j-2][k] * C(2, m-k-(j-2))) % mod;
if(k) cnt=(cnt + f[i-1][j-1+1][k-1] * (m-(k-1)-j) * (j-1+1)) % mod;
if(k>1) cnt=(cnt + f[i-1][j+2][k-2] * C(2, j+2)) % mod;
}
}
}

//最后这点根据加法原理,应该不用多讲
long long ans=0;
for(int j=0; j<=m; j++)
{
for(int k=0; k<=m; k++)
{
if(j+k<=m)
{
ans=(ans + f
[j][k]) % mod;
}
}
}
printf("%lld\n",ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: