P2051 [AHOI2009]中国象棋 状压dp
思路
因 为 在 同 一 行 或 同 一 列 中 最 多 放 两 个 炮 , 所 以 可 以 放 0 、 1 、 2 个 炮 , 三 个 以 上 的 就 不 合 法 , 因 为 可 以 互 相 打 。 因为在同一行或同一列中最多放两个炮,所以可以放0、1、2个炮,三个以上的就不合法,因为可以互相打。 因为在同一行或同一列中最多放两个炮,所以可以放0、1、2个炮,三个以上的就不合法,因为可以互相打。
对 于 状 态 压 缩 d p , 首 先 需 要 假 设 状 态 , 这 题 的 状 态 就 是 0 、 1 、 2 , ( 列 是 指 第 i 行 中 1 m 列 ) 0 代 表 一 列 中 没 有 炮 , 1 代 表 一 列 中 有 一 个 炮 , 2 代 表 一 列 中 有 2 个 炮 , 从 第 一 行 往 下 遍 历 , 寻 找 所 有 的 状 态 。 对于状态压缩dp,首先需要假设状态,这题的状态就是0、1、2,(列是指第i行中1~m列)0代表一列中没有炮,1代表一列中有一个炮,2代表一列中有2个炮,从第一行往下遍历,寻找所有的状态。 对于状态压缩dp,首先需要假设状态,这题的状态就是0、1、2,(列是指第i行中1 m列)0代表一列中没有炮,1代表一列中有一个炮,2代表一列中有2个炮,从第一行往下遍历,寻找所有的状态。
所 以 我 们 定 义 一 个 数 组 d p [ i ] [ j ] [ k ] , 第 一 维 表 示 前 i 行 , 第 二 维 表 示 有 j 列 只 有 一 个 炮 , 第 三 维 表 示 只 有 k 列 有 两 个 炮 \red{所以我们定义一个数组dp[i][j][k],第一维表示前i行,第二维表示有j列只有一个炮,第三维表示只有k列有两个炮} 所以我们定义一个数组dp[i][j][k],第一维表示前i行,第二维表示有j列只有一个炮,第三维表示只有k列有两个炮
那 么 根 据 容 斥 原 理 可 知 , 没 有 炮 的 列 数 有 m − j − k 列 。 \red{那么根据容斥原理可知,没有炮的列数有m-j-k列。} 那么根据容斥原理可知,没有炮的列数有m−j−k列。
因
为
一
列
中
最
多
放
两
个
棋
子
,
且
对
于
每
一
行
,
每
一
列
只
能
放
一
个
,
不
能
说
放
两
个
,
难
道
让
他
们
结
合
吗
?
这
里
笔
者
就
想
当
然
的
结
合
了
,
然
后
就
错
了
。
因为一列中最多放两个棋子,且对于每一行,每一列只能放一个,不能说放两个,难道让他们结合吗?这里笔者就想当然的结合了,然后就错了。
因为一列中最多放两个棋子,且对于每一行,每一列只能放一个,不能说放两个,难道让他们结合吗?这里笔者就想当然的结合了,然后就错了。
所
以
我
们
可
以
讨
论
以
下
三
种
情
况
(
所
有
情
况
的
第
i
行
都
是
由
第
i
−
1
行
继
承
下
来
)
:
所以我们可以讨论以下三种情况(所有情况的第i行都是由第i-1行继承下来):
所以我们可以讨论以下三种情况(所有情况的第i行都是由第i−1行继承下来):
-
不
放
棋
子
\blue{不放棋子}
不放棋子
第 i 行 的 状 态 可 以 由 第 i − 1 行 状 态 继 承 , 即 d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j ] [ k ] 。 第i行的状态可以由第i-1行状态继承,即\red{dp[i][j][k]=dp[i - 1][j][k]}。 第i行的状态可以由第i−1行状态继承,即dp[i][j][k]=dp[i−1][j][k]。 -
放
一
个
棋
子
\blue{放一个棋子}
放一个棋子
该 棋 子 放 在 有 一 个 棋 子 的 列 上 \blue{该棋子放在有一个棋子的列上} 该棋子放在有一个棋子的列上
在 j 列 中 拿 出 一 列 + 1 个 棋 子 成 为 k 列 中 的 一 员 , 即 d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j ] [ k + 1 ] ∗ ( j + 1 ) 在j列中拿出一列+1个棋子成为k列中的一员,即\red{dp[i][j][k]=dp[i-1][j][k+1]*(j+1)} 在j列中拿出一列+1个棋子成为k列中的一员,即dp[i][j][k]=dp[i−1][j][k+1]∗(j+1)
该 棋 子 放 在 空 列 上 \blue{该棋子放在空列上} 该棋子放在空列上
在 空 列 中 拿 出 一 列 成 为 j 列 的 一 员 , 即 d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j − 1 ] [ k ] ∗ ( m − j − k + 1 ) 在空列中拿出一列成为j列的一员,即\red{dp[i][j][k]=dp[i-1][j-1][k]*(m-j-k+1)} 在空列中拿出一列成为j列的一员,即dp[i][j][k]=dp[i−1][j−1][k]∗(m−j−k+1) -
放
两
个
棋
子
\blue{放两个棋子}
放两个棋子
两 个 棋 子 分 别 放 在 两 个 空 列 上 \blue{两个棋子分别放在两个空列上} 两个棋子分别放在两个空列上
在 空 列 中 拿 出 两 列 成 为 j 列 的 两 员 , 即 d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j − 2 ] [ k ] ∗ C m − j − k + 2 2 在空列中拿出两列成为j列的两员,即\red{dp[i][j][k]=dp[i-1][j-2][k]*C_{m-j-k+2}^2} 在空列中拿出两列成为j列的两员,即dp[i][j][k]=dp[i−1][j−2][k]∗Cm−j−k+22 -
两
个
棋
子
一
个
在
有
棋
子
的
列
一
个
在
空
列
中
\blue{两个棋子一个在有棋子的列一个在空列中}
两个棋子一个在有棋子的列一个在空列中
在 j 列 中 拿 出 一 个 成 为 k 列 的 一 员 , 在 空 列 中 拿 出 一 个 成 为 j 列 的 一 员 , 即 d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j ] [ k − 1 ] ∗ j ∗ ( m − j − k + 1 ) 在j列中拿出一个成为k列的一员,在空列中拿出一个成为j列的一员,即\red{dp[i][j][k]=dp[i-1][j][k-1]*j*(m-j-k+1)} 在j列中拿出一个成为k列的一员,在空列中拿出一个成为j列的一员,即dp[i][j][k]=dp[i−1][j][k−1]∗j∗(m−j−k+1) -
两
个
棋
子
分
别
放
在
两
个
有
棋
子
的
列
上
\blue{两个棋子分别放在两个有棋子的列上}
两个棋子分别放在两个有棋子的列上
在 j 列 拿 出 两 个 成 为 k 列 的 两 员 , 即 d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j + 2 ] [ k − 2 ] ∗ C j + 2 2 在j列拿出两个成为k列的两员,即\red{dp[i][j][k]=dp[i-1][j+2][k-2]*C_{j+2}^2} 在j列拿出两个成为k列的两员,即dp[i][j][k]=dp[i−1][j+2][k−2]∗Cj+22 -
两
个
棋
子
放
在
没
有
棋
子
的
一
列
上
\blue{两个棋子放在没有棋子的一列上}
两个棋子放在没有棋子的一列上
这 是 错 误 的 , 上 面 说 道 , 对 于 每 一 行 , 只 能 在 一 列 中 放 一 个 , 不 能 放 两 个 , 否 则 就 会 “ 结 合 ” 了 。 \red{这是错误的,上面说道,对于每一行,只能在一列中放一个,不能放两个,否则就会“结合”了。} 这是错误的,上面说道,对于每一行,只能在一列中放一个,不能放两个,否则就会“结合”了。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef pair<int, int> pdd;
#define INF 0x7f7f7f
#define mem(a, b) memset(a , b , sizeof(a))
#define FOR(i, x, n) for(int i = x;i <= n; i++)
// const ll mod = 998244353;
// const int maxn = 1e5 + 10;
// const double eps = 1e-6;
const int mod = 9999973;
ll dp[105][105][105];
int n, m;
void solve() {
cin >> n >> m;
dp[0][0][0] = 1;
for(int i = 1;i <= n; i++) {
for(int j = 0;j <= m; j++) {
for(int k = 0;k <= m - j; k++) {
dp[i][j][k] = dp[i - 1][j][k]; // 不放棋子
if(k >= 1) dp[i][j][k] += dp[i - 1][j + 1][k - 1] * (j + 1); // 放一个棋子在有一个棋子的列
if(j >= 1) dp[i][j][k] += dp[i - 1][j - 1][k] * (m - j + 1 - k); // 放一个棋子在没有棋子的列
if(j >= 2) dp[i][j][k] += dp[i - 1][j - 2][k] * ((m - j - k + 2) * (m - j - k + 1) / 2); // 放两个棋子在没有棋子的两列
if(k >= 1) dp[i][j][k] += dp[i - 1][j][k - 1] * j * (m - j - k + 1); // 放两个在一列有棋子一列无棋子
if(k >= 2) dp[i][j][k] += dp[i - 1][j + 2][k - 2] * ((j + 2) * (j + 1) / 2); // 放两个在两列都有棋子
dp[i][j][k] %= mod;
}
}
}
ll ans = 0;
for(int j = 0;j <= m; j++) {
for(int k = 0;k <= m - j; k++) {
ans = (ans + dp[n][j][k]) % mod;
}
}
cout << ans << endl;
}
signed main() {
ios_base::sync_with_stdio(false);
//cin.tie(nullptr);
//cout.tie(nullptr);
#ifdef FZT_ACM_LOCAL
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
signed test_index_for_debug = 1;
char acm_local_for_debug = 0;
do {
if (acm_local_for_debug == '$') exit(0);
if (test_index_for_debug > 20)
throw runtime_error("Check the stdin!!!");
auto start_clock_for_debug = clock();
solve();
auto end_clock_for_debug = clock();
cout << "Test " << test_index_for_debug << " successful" << endl;
cerr << "Test " << test_index_for_debug++ << " Run Time: "
<< double(end_clock_for_debug - start_clock_for_debug) / CLOCKS_PER_SEC << "s" << endl;
cout << "--------------------------------------------------" << endl;
} while (cin >> acm_local_for_debug && cin.putback(acm_local_for_debug));
#else
solve();
#endif
return 0;
}