1. 程式人生 > >洛谷 P2051 [AHOI2009]中國象棋

洛谷 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\n", ans);
	
	return 0;
}