AHOI 2009 中国象棋
题面
题目描述
这次小可可想解决的难题和中国象棋有关,在一个N行M列的棋盘上,让你放若干个炮(可以是0个),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法。大家肯定很清楚,在中国象棋中炮的行走方式是:一个炮攻击到另一个炮,当且仅当它们在同一行或同一列中,且它们之间恰好 有一个棋子。你也来和小可可一起锻炼一下思维吧!
输入输出格式
输入格式:
一行包含两个整数N,M,之间由一个空格隔开。
输出格式:
总共的方案数,由于该值可能很大,只需给出方案数模9999973的结果。
输入输出样例
输入样例#1:
1 3
输出样例#1:
7
说明
样例说明
除了3个格子里都塞满了炮以外,其它方案都是可行的,所以一共有222-1=7种方案。
数据范围
100%的数据中N和M均不超过100
50%的数据中N和M至少有一个数不超过8
30%的数据中N和M均不超过6
Solution
乍看到这题, 真的没什么思路.
找到的比较靠谱的解法是这样的: 我们从上往下一行一行放置棋子, 用f[i][j][k]
表示在前\(i\)行中, \(j\)列上有\(1\)个棋子, \(k\)列上有\(2\)个棋子的合法方案数. 考虑怎么转移: 开始时我考虑的是递归式, 发现式子的形式非常复杂, 有许多细节需要考虑, 因此改为递推式.
考虑在已经放置好的前\(i\)列的基础上, 在第\(i + 1\)列上放棋子. 我们可以在这一行上放\(0\)或\(1\)或\(2\)颗棋子, 并且要求这些棋子所在的列原来最多只能有\(1\)颗棋子. 暴力转移即可. 时间复杂度: \(O(nm^2)\).
#include <cstdio>
#include <cstring>
typedef long long LL;
const int N = 100, M = 100, MOD = 9999973;
int n, m;
int f[N + 7][M + 7][M + 7];
inline void plus(int &a, LL b) { a = (a + b) % MOD; }
int main()
{
scanf("%d%d", &n, &m);
memset(f, 0, sizeof f);
f[0][0][0] = 1;
for (int i = 0; i < n; ++ i) for (int j = 0; j <= m; ++ j) for (int k = 0; k <= m; ++ k) if (f[i][j][k])
{
/* f[i][j][k] = f[i - 1][j][k];
if (j) plus(f[i][j][k], (LL)f[i - 1][j - 1][k] * (m - j - k + 1));
if (k) plus(f[i][j][k - 1], (LL)f[i - 1][j + 1][k - 1] * (j + 1));
if (j >= 2) plus(f[i][j - 2][k], (LL)f[i - 1][j - 2][k] * (m - j - k + 2) * (m - j - k + 1) / 2);
if (k >= 2) plus(f[i][j][k], (LL)f[i - 1][j + 2][k - 2] * (j + 2) * (j + 1) / 2);
if (j && k) plus(f[i][j][k], (LL)f[i - 1][j][k - 1] * (m - j - k + 1) * j); */
plus(f[i + 1][j][k], f[i][j][k]);
if (j + k < m) plus(f[i + 1][j + 1][k], (LL)f[i][j][k] * (m - j - k));
if (j) plus(f[i + 1][j - 1][k + 1], (LL)f[i][j][k] * j);
if (j + k <= m - 2) plus(f[i + 1][j + 2][k], (LL)f[i][j][k] * (m - j - k) * (m - j - k - 1) / 2);
if (j >= 2) plus(f[i + 1][j - 2][k + 2], (LL)f[i][j][k] * j * (j - 1) / 2);
if (j + k < m && j) plus(f[i + 1][j][k + 1], (LL)f[i][j][k] * (m - j - k) * j);
}
int ans = 0;
for (int i = 0; i <= m; ++ i) for (int j = 0; j <= m; ++ j) plus(ans, f[n][i][j]);
printf("%d\n", ans);
}