动态规划/递推 - 中国象棋
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行的某一列能否下棋,其实是要取决于之前的每列下了多少枚棋的。因此,我们可以考虑这个状态:
看我1 2 0的顺序,你也应该知道了:状态l是可以被砍掉的。因为如果知道了有j列上下了一枚棋,有k列上下了两枚棋,那么没有下棋的列是可以算出来的,为m-j-k。这满足了“准确说明一种情况”的原则。因此新的状态为:
所以就可以考虑状态转移方程了。在根据放前i-1行的方案总数计算前i行的方案总数时在分情况时应遵循加法原理。
可以分6种情况:
1.若第i行一个都不放,就是这么多(见代码)
2.若第i行放1个
I.若都放在了之前一个都没有放的列上,则要用到
如果以上推导的思路懂了,后面的就能自己推导了。
II.放在了之前已经放了一个的列上。这样会使放了两个的列的列数加1,所以要用到
3.若第i行放2个
I.都放在之前一个都没有放的列上。乘上的数为
II.一个放在之前没有放的列上,一个放在之前放了一个的列上。乘上的数为
III.两个都放在之前放了一个的列上。乘上的数为
当然,这只是思路,还要加上一些条件,见完整参考代码。
参考代码
总时间限制: 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; }
相关文章推荐
- NYOJ 题目252 01串(动态规划,递推)
- [BZOJ1801][AHOI2009]中国象棋(递推)
- BZOJ 1801 [Ahoi2009]chess 中国象棋 递推
- 100道动态规划——7 UVA 1630 folding 因为自己考虑的不周全WA了好几发。。。递推,KMP求子串周期
- 动态规划____数塔 递推方法(以前都是记忆化搜索)
- 谈谈程序员的职业规划--从中国象棋所想到的
- 动态规划(DP),递推,最大子段和,POJ(2479,2593)
- 动态规划 问题之数字三角形(正序递推)
- COJ 1022: 菜鸟和大牛(简单的动态规划,递推)
- 动态规划之简单递推(HDU2041,HDU2044,HDU2045,HDU2046,HDU2047)
- 动态规划——递推 兔子和奶牛
- 动态规划之简单递推——hdu2577
- BZOJ 1801 AHOI2009 中国象棋 递推
- 动态规划,递推,高精度(Matches,uva 11375)
- 动态规划简单例子--象棋步骤
- 动态规划的记忆搜索与递推
- 动态规划与递推——动态规划是最优化算法 ( 转自2004chen)
- 100道动态规划——13 UVA 10163 Storage Keepers 有约束条件下的DP,递推,不能使用结构体作为基本单位
- 基础动态规划题 火车站(上车下车)——递推
- 100道动态规划——26 UVA 12099 The Bookcase 状态的定义,递推,背包