洛谷 P2051 [AHOI2009]中国象棋

这道题主要是状态很难想到
首先可以看出每行每列不能超过2个棋子
也就是说有0, 1, 2三种状态

所以可以一行一行来处理
那就用f[i][j][k]表示前i行,有j列放了一个棋子,有k列放了2个棋子的方案数
放了0个棋子的列数是r=m-j-kr=m-j-k
那么这个时候状态转移方程就非常好写了。
对于当前这一行可以不放,放一个,放两个棋子
0表示没有棋子的列,1表示有1个棋子的列。
那么有几种情况

不放
放一个在0
放一个在1
放两个都在0
放两个一个0一个1
放两个在1

放两个一个0一个1为例
方程为f[i+1][j][k+1] = (f[i+1][j][k+1] +f[i][j][k] * r * j)
这里用刷表法,比填表法方便非常多,但是要注意有些状态是不存在的(比如kj都等于m
所以一开始要判断之前有没有刷到过这个状态

现在解释一个这个方程
现在是第i行,要填i+1行
放一个在0的话,就有一个0变成1
所以j要加1
放一个在1的话,就有一个1变成2
所以j要减1,k要加1
这里0的列数不用管,因为推出j和k就可以知道0的列数了(m-j-k)
所以j加1又减1,所以不变,而k+1
所以是f[i+1][j][k+1]
那么放一个在0一个在1的方法显然是0的列数乘上1的列数
所以就是 f[i][j][k] * r * j

最后就是把最后一行的所有情况加起来就是答案
注意可以用define来简化代码

#include<cstdio>
#include<algorithm>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define cal(a, b) a = (a + b) % MOD
using namespace std;

const int MOD = 9999973;
const int MAXN = 112;
long long f[MAXN][MAXN][MAXN], n, m, ans;

int main()
{
	scanf("%d%d", &n, &m);
	f[0][0][0] = 1;
	REP(i, 0, n)
		_for(j, 0, m)
			for(int k = 0; k + j <= m; k++)
			{
				if(!f[i][j][k]) continue;
				int r = m - j - k;
				cal(f[i+1][j][k], f[i][j][k]);
				if(r >= 1) cal(f[i+1][j+1][k], f[i][j][k] * r);
				if(j >= 1) cal(f[i+1][j-1][k+1], f[i][j][k] * j);
				if(r >= 2) cal(f[i+1][j+2][k], f[i][j][k] * (r * (r - 1) / 2));
				if(r >= 1 && j >= 1) cal(f[i+1][j][k+1], f[i][j][k] * r * j);
				if(j >= 2) cal(f[i+1][j-2][k+2], f[i][j][k] * (j * (j - 1) / 2));
			}
	
	_for(j, 0, m)
		for(int k = 0; k + j <= m; k++)
			cal(ans, f[n][j][k]);
	printf("%lld
", ans);
	
	return 0;
}


 

原文地址:https://www.cnblogs.com/sugewud/p/9819365.html