Skip to content

「AHOI2009」中国象棋

检测到 KaTeX 加载失败,可能会导致文中的数学公式无法正常渲染。

#题面

#题目描述

这次小可可想解决的难题和中国象棋有关,在一个 nnmm 列的棋盘上,让你放若干个炮(可以是 00 个),使得没有一个炮可以攻击到另一个炮,请问有多少种放置方法。大家肯定很清楚,在中国象棋中炮的行走方式是:一个炮攻击到另一个炮,当且仅当它们在同一行或同一列中,且它们之间恰好 有一个棋子。你也来和小可可一起锻炼一下思维吧!

#输入格式

一行包含两个整数 n,mn, m,之间由一个空格隔开。

#输出格式

总共的方案数,由于该值可能很大,只需给出方案数模 99999739999973 的结果。

#输入输出样例

样例输入 #1

1 3

样例输出 #1

7

样例解释 #1

除了 33 个格子里都塞满了炮以外,其它方案都是可行的,所以一共有 2×2×21=72 \times 2 \times 2-1=7 种方案。

#数据范围与提示

  • 对于 30%30\% 的数据,nnmm 均不超过 66
  • 对于 50%50\% 的数据,nnmm 至少有一个数不超过 88
  • 对于 100%100\% 的数据,1n,m1001 \leq n,m \leq 100

#思路

显然,一行或者一列上不能放置 >2> 2 个炮。

fi,j,kf_{i, j, k} 表示在前 ii 行时有 jj 行放了 11 个炮、kk 行放了 22 个炮,则有 ijki - j - k 行没放炮。

当到达第 ii 行的时候,有以下几种情况:

  1. 不新增炮,此时有转移方程:

    fi,j,kfi1,j,kf_{i, j, k} \leftarrow f_{i - 1, j, k}

  2. 新增 11 个炮。

    1. 00 个炮增加到 11 个炮:

      fi,j,kfi1,j1,k×(mjk+1)f_{i, j, k} \leftarrow f_{i - 1, j - 1, k} \times (m - j - k + 1)

    2. 11 个炮增加到 22 个炮:

      fi,j,kfi1,j+1,k1×(j+1)f_{i, j, k} \leftarrow f_{i - 1, j + 1, k - 1} \times (j + 1)

  3. 新增 22 个炮。

    1. 一列从 00 个炮增加到 11 个炮,一列从 11 个炮增加到 22 个炮:

      fi,j,kfi1,j,k1×j×(mjk+1)f_{i, j, k} \leftarrow f_{i - 1, j, k - 1} \times j \times (m - j - k + 1)

    2. 两列从 00 个炮增加到 11 个炮:

      fi,j,kfi1,j2,k×(mjk+22)f_{i, j, k} \leftarrow f_{i - 1, j - 2, k} \times \binom{m - j - k + 2}{2}

    3. 两列从 11 个炮增加到 22 个炮:

      fi,j,kfi1,j+2,k2×(j2)f_{i, j, k} \leftarrow f_{i - 1, j + 2, k - 2} \times \binom{j}{2}

直接转移,最后枚举结束点并计算其到车站的距离即可。

#代码

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <iostream>

using std::cin;
using std::cout;
const char endl = '\n';

const int N = 105;
const int mod = 9999973;

int n, m;
long long f[N][N][N]{1}, ans;

long long C(long long x) {
return x * (x - 1) / 2;
}

int main() {
std::ios::sync_with_stdio(false);
cin.tie(nullptr);

cin >> n >> m;

for (int i = 1; i <= n; i++) {
// 不增加炮
for (int j = 0; j <= m; j++) {
for (int k = 0; j + k <= m; k++) {
f[i][j][k] = f[i - 1][j][k];
}
}

// 增加 1 个炮
//
// 从 0 个炮增加到 1 个炮
for (int j = 1; j <= m; j++) {
for (int k = 0; j + k <= m; k++) {
f[i][j][k] = (f[i][j][k] + f[i - 1][j - 1][k] * (m - j - k + 1) % mod) % mod;
}
}

// 增加 1 个炮
//
// 从 1 个炮增加到 2 个炮
for (int j = 0; j <= m; j++) {
for (int k = 1; j + k <= m; k++) {
f[i][j][k] = (f[i][j][k] + f[i - 1][j + 1][k - 1] * (j + 1) % mod) % mod;
}
}

// 增加 2 个炮
//
// 一列从 0 个炮增加到 1 个炮
// 一列从 1 个炮增加到 2 个炮
for (int j = 0; j <= m; j++) {
for (int k = 1; j + k <= m; k++) {
f[i][j][k] = (f[i][j][k] + f[i - 1][j][k - 1] * j * (m - j - k + 1) % mod) % mod;
}
}

// 增加 2 个炮
//
// 两列从 0 个炮增加到 1 个炮
for (int j = 2; j <= m; j++) {
for (int k = 0; j + k <= m; k++) {
f[i][j][k] = (f[i][j][k] + f[i - 1][j - 2][k] * C(m - j - k + 2) % mod) % mod;
}
}

// 增加 2 个炮
//
// 两列从 1 个炮增加到 2 个炮
for (int j = 0; j <= m; j++) {
for (int k = 2; j + k <= m; k++) {
f[i][j][k] = (f[i][j][k] + f[i - 1][j + 2][k - 2] * C(j + 2) % mod) % mod;
}
}
}

for (int i = 0; i <= m; i++) {
for (int j = 0; i + j <= m; j++) {
ans = (ans + f[n][i][j]) % mod;
}
}

cout << ans << endl;

return 0;
}