题解 P5363 【[SDOI2019]移动金币】
首先我们会发现最后一枚棋子的移动是与别的棋子有所不同的,移动别的棋子的时候,并没有改变走向最终结束状态的步数,相当于是把它后面一段空格内的一些格子“移动”到了上一段空格内。而如果走最后一个,就相当于把后面一段空格里的格子扔掉了。
咦,这不阶梯博弈吗?
那么我们现在的问题就转化为了,我们要用
这个看起来十分的不可做,但是我们想,异或为0即对于每一位,这一位是1的个数有偶数个。所以,我们可以考虑每个盒子的棋子个数二进制拆分成若干个不同的
那么就可以dp了,
那么我们最终把剩下的格子用插板法放到奇盒子里即可。跑得比我想象的快多了
上代码~
#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
#define p 1000000009
#define md(_o) (((_o) >= p) ? ((_o)-p) : (_o))
using namespace std;
namespace ywy {
inline ll mi(int a, int b) {
ll ans = 1, tmp = a;
while (b) {
if (b & 1)
ans = (ans * tmp) % p;
tmp = (tmp * tmp) % p;
b >>= 1;
}
return (ans);
}
ll jc[200001], jcny[200001];
inline ll cnm(int n, int m) {
if (n < m || m < 0)
return (0);
ll cjr = jc[n];
cjr *= jcny[m];
cjr %= p;
cjr *= jcny[n - m];
return (cjr % p);
}
inline ll chaban(int n, int m) {
if (n == 0 && m == 0)
return (1);
return (cnm(n + m - 1, m - 1));
}
ll dp[21][150001];
inline void pre(int n) {
jc[0] = 1;
for (register int i = 1; i <= n; i++) jc[i] = (jc[i - 1] * i) % p;
jcny[n] = mi(jc[n], p - 2);
for (register int i = n - 1; i >= 0; i--) jcny[i] = (jcny[i + 1] * (i + 1)) % p;
}
void ywymain() {
int n, m;
cin >> n >> m;
if (n < m) {
cout << 0 << endl;
return;
}
pre(n + m);
int k = 1;
while ((1 << k) <= n) k++;
dp[k][n - m] = 1;
for (register int i = k - 1; i >= 0; i--) {
for (register int j = 0; j <= n - m; j++) {
if (!dp[i + 1][j])
continue;
for (register int k = 0; k <= (m + 1) / 2 && k * (1 << i) <= j; k += 2) {
dp[i][j - k * (1 << i)] = (dp[i][j - k * (1 << i)] + dp[i + 1][j] * cnm((m + 1) / 2, k)) % p;
}
}
}
ll ans = 0;
for (register int i = 0; i <= n - m; i++) ans = (ans + dp[0][i] * chaban(i, (m + 2) / 2)) % p;
cout << (cnm(n, m) + p - ans) % p << endl;
}
}
int main() {
ywy::ywymain();
return (0);
}