# BZOJ4806_炮
解:
本质上就是说,每行或每列上不能放大于等于三个炮
考虑这样一个dp:
令f[i][j][k]表示:前i行,有j列没放,有k列放一个,((m - j - k)列放了两个)
则有如下转移:
1、这一行不放,\(f[i][j][k] = f[i - 1][j][k]\)
2、这一行放一个:
2.1、这一个放在一个没放的列上:\(f[i][j][k] += f[i - 1][j + 1][k - 1] * C_{j + 1}^{1}\)
2.2、这一个放在一个有一个的列上:\(f[i][j][k] += f[i - 1][j][k + 1] * C_{k + 1}^{1}\)
3、这一行放两个:
3.1、两个都放在没放的列上: \(f[i][j][k] += f[i - 1][j + 2][k - 2] * C_{j + 2}^{2}\)
3.2、两个一个放在没放的列上,一个放在一个的列上: \(f[i][j][k] += f[i - 1][j + 1][k] * C_{j + 1}^{1} * C_{k}^{1}\)
3.3、两个都放在一个的列上: \(f[i][j][k] += f[i - 1][j][k + 2] * C_{k + 2}^{2}\)
按照如上方程逐个转移即可
特别的,初始化:
先算出第一行的答案,
令 \(f[1][m][0] = 1\)
令 \(f[1][m - 1][1] = C_{m}^{1} = m\)
令 \(f[1][m - 2][2] = C_{m}^{2}\)
然后i从第二行开始枚举
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int md = 999983;
ll n, m, ans;
ll f[110][110][110];
ll C(ll x, ll y) {
if (y == 1) return x;
return ((x * (x - 1)) / 2) % md;
}//因为y只有1和2两种取值,所以就简写了
//1079364439
int main() {
scanf("%lld%lld", &n, &m);
f[1][m][0] = 1;
f[1][m - 1][1] = m;
f[1][m - 2][2] = C(m, 2);
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
for (int k = 0; k + j <= m; k++) {
f[i][j][k] = (f[i][j][k] + f[i - 1][j][k]) % md;
if (k >= 1 && j + 1 <= m) f[i][j][k] =
(f[i][j][k] + f[i - 1][j + 1][k - 1] * C(j + 1, 1)) % md;
if (k + 1 <= m) f[i][j][k] =
(f[i][j][k] + f[i - 1][j][k + 1] * C(k + 1, 1)) % md;
if (k >= 2 && j + 2 <= m) f[i][j][k] =
(f[i][j][k] + f[i - 1][j + 2][k - 2] * C(j + 2, 2)) % md;
if (j + 1 <= m) f[i][j][k] =
(f[i][j][k] + f[i - 1][j + 1][k] * C(j + 1, 1) * C(k, 1)) % md;
if (k + 2 <= m) f[i][j][k] =
(f[i][j][k] + f[i - 1][j][k + 2] * C(k + 2, 2)) % md;
}
}
}
for (int j = 0; j <= m; j++)
for (int k = 0; k <= m; k++)
ans = (ans + f[n][j][k]) % md;
cout << ans << endl;
return 0;
}
一定看清模数到底是多少,不要像我一样查半天最后发现是模数打错了

浙公网安备 33010602011771号