洛谷P2051 中国象棋(dp)
题目链接:传送门
题目大意:
在N行M列的棋盘中放象棋中的“炮”,问要使得“炮”两两互不伤害,有多少种放法。
1 ≤ n,m ≤ 100,答案对9999973取模。
思路:
按行更新答案。每行炮可以放在空列(下称A列)和有一个炮的列(下称B列),从而生成B列和有两个炮的列(C列),所以更新行的时候有这样几种选择:
①不放“炮”;
②选一个A列放一个“炮”,生成一个B列;
③选一个B列放一个“炮”,生成一个C列;
④选两个A列放一个“炮”,生成两个B列;
⑤选两个B列放一个“炮”,生成两个C列;
⑥选一个A列和一个B列各放一个“炮”,生成一个B列和一个C列;(注意在同一行不能放两个棋子在同一列,所以不能看作用一个A列生成一个C列)
考虑到各行各列的顺序与答案无关:
状态:
f[i][j][k]:第i行有j个B列和k个C列。
初始状态:
f[0][0][0] = 1;
状态转移方程:
①f[i][j][k] = f[i-1][j][k];
②f[i][j][k] = f[i-1][j-1][k] ×(i-1行A列个数);
③f[i][j][k] = f[i-1][j+1][k-1] ×(i-1行B列个数);
④f[i][j][k] = f[i-1][j-2][k] ×(i-1行A列个数选2);
⑤f[i][j][k] = f[i-1][j+2][k-2] ×(i-1行B列个数选2);
⑥f[i][j][k] = f[i-1][j][k-1] ×(i-1行A列个数)×(i-1行B列个数);
注意边界和取模即可。
时间复杂度:O(nm2)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int MAX_N = 105; const int MOD = 9999973; ll f[MAX_N][MAX_N][MAX_N]; inline int C(int n) {//n选2 return n*(n-1)/2; } int main() { int N, M; cin >> N >> M; f[0][0][0] = 1; for (int i = 1; i <= N; i++) { for (int j = 0; j <= M; j++) { for (int k = 0; k+j <= M; k++) { //放1个 if (j-1 >= 0 && (M-(j-1+k)) >= 1) f[i][j][k] = (f[i][j][k] + f[i-1][j-1][k] * (M-(j-1+k))) % MOD; if (j+1 <= M && k >= 1) f[i][j][k] = (f[i][j][k] + f[i-1][j+1][k-1] * (j+1)) % MOD; //放两个 if (j-2 >= 0 && (M-(j-2+k)) >= 2) f[i][j][k] = (f[i][j][k] + f[i-1][j-2][k] * C(M-(j-2+k))) % MOD; if (k >= 1 && (M-(j+k-1)) >= 1 && j >= 1) f[i][j][k] = (f[i][j][k] + f[i-1][j][k-1] * (M-(j+k-1)) * j) % MOD; if (k >= 2) f[i][j][k] = (f[i][j][k] + f[i-1][j+2][k-2] * C(j+2)) % MOD; //不放 f[i][j][k] = (f[i][j][k] + f[i-1][j][k]) % MOD; } } } ll ans = 0; for (int j = 0; j <= M; j++) for (int k = 0; k+j <= M; k++) ans = (ans + f[N][j][k]) % MOD; cout << ans << endl; return 0; }