「SDOI2011」 黑白棋 题解(dp 部分)
绷不住了,洛谷上的 dp 没一个表述清楚了,一怒之下写一篇题解。注意本题解只讲 dp 部分。前面的 k-Nim 部分请去看其他题解或者自学。
首先转化不合法的充要条件就是:设相邻两个棋子中间间隔数量为 \(b\),那么对于任意非负整数 \(i\) 都有 \((d+1)|\sum [b\& 2^i>0]\)。其中 \(\&\) 是按位与运算。所以我们要计数一个有序的并且包含 \(\dfrac{k}{2}\) 个元素的 \(b\) 数组。
那么我们考虑 \(f_{i,j}\) 表示这 \(\dfrac{k}{2}\) 个数每个都填了二进制上前 \(i\) 位并且这 \(i\) 位满足是 \(d+1\) 倍数的条件,并且选出数字总和为 \(j\) 的方案数。那么这时我们就可以枚举第 \(i+1\) 位的填数字情况,很显然是要填 \(p(d+1)\) 个 \(1\),其中 \(p\in\mathbf{N}\)。我们就枚举 \(p\),那么总和增量就是 \(2^i\times p(d+1)\);我们可以考虑哪些位置选了数,那么产生的贡献就是 \(f_{i,j}\times\left(\begin{matrix}\dfrac{k}{2}\\p(d+1)\end{matrix}\right)\) 这么多,加到 \(f_{i,j+2^i\times p(d+1)}\) 上。
最后我们要通过 \(b\) 数组还原棋子的排布情况,那么我们枚举 \(j\),由于有 \(j+\dfrac{k}{2}\) 个位置是不能选的(间隔和终点),那么剩余的位置数量是 \(n-j-\dfrac{k}{2}\),\(j\) 对方案的贡献就是 \(f_{L,j}\times\left(\begin{matrix}n-j-\dfrac{k}{2}\\\dfrac{k}{2}\end{matrix}\right)\) 这么多。其中 \(L\) 是最高位。最后用 \(\left(\begin{matrix}n\\k\end{matrix}\right)\) 减去不合法答案的数量即可。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 10005, L = 25, K = 105, mod = 1000000007;
int n, k, d, f[L][N], C[N][K];
inline void add(int &x, int y) {
x = (x + y) % mod;
}
int main() {
scanf("%d %d %d", &n, &k, &d);
int lim = __lg(n);
C[0][0] = 1;
for (int i = 1; i <= n; i++) {
C[i][0] = 1;
for (int j = 1; j <= k; j++) {
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
}
}
f[0][0] = 1;
for (int i = 0; i <= lim; i++) {
for (int j = 0; j <= n; j++) {
for (int x = 0; j + (x << i) * (d + 1) <= n && x * (d + 1) <= k / 2; x++) {
add(f[i + 1][j + (x << i) * (d + 1)], 1ll * f[i][j] * C[k / 2][x * (d + 1)] % mod);
}
}
}
int ans = 0;
for (int i = 0; i <= n; i++) {
if (n - i - k / 2 >= 0) add(ans, 1ll * f[lim + 1][i] * C[n - i - k / 2][k / 2] % mod);
}
ans = (C[n][k] - ans + mod) % mod;
printf("%d", ans);
return 0;
}

浙公网安备 33010602011771号