luogu P5363 [SDOI2019]移动金币
https://www.luogu.com.cn/problem/P5363
阶梯Nim板板题
让我们回忆一下阶梯Nim是什么?
就是对于把偶数的层的当作垃圾桶,没有用,因为把偶数层上的往下移一层,对手可以把你移动的那些再往下移动一层,所以偶数层的没有用
所以直接把奇数层的个数异或起来,看一下是否为0即可
对于这题,倒过来考虑,最后一个金币后面的相当于是地板,然后两个金币之间的相当于是一层石子的个数
考虑DP,\(dp[i][j]\)表示考虑最后奇数层\(xor\)的前\(i\)位,还剩下\(j\)个石子没放,\(xor\)和为\(0\)的方案数
用组合数瞎转移一下即可
然后最后用插板法算一下就行了
code:
#include<bits/stdc++.h>
#define N 600050
#define mod 1000000009
#define ll long long
using namespace std;
ll qpow(ll x, ll y) {
ll ret = 1;
for(; y; y >>= 1, x = x * x % mod) if(y & 1) ret = ret * x % mod;
return ret;
}
ll fac[N], ifac[N];
void init(int n) {
fac[0] = 1;
for(int i = 1; i <= n; i ++) fac[i] = fac[i - 1] * i % mod;
ifac[n] = qpow(fac[n], mod - 2);
for(int i = n - 1; i >= 0; i --) ifac[i] = ifac[i + 1] * (i + 1) % mod;
}
ll C(int n, int m) {
if(n < m) return 0;
return fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}
ll calc(int n, int m) {
return C(n + m - 1, m - 1);
}
int n, m;
ll dp[33][N];
int main() {
scanf("%d%d", &n, &m); init(n + m);
int lim = 1;
for(; (1 << lim) <= n; ) lim ++;
dp[lim][n - m] = 1;
for(int i = lim - 1; i >= 0; i --) {
for(int j = 0; j <= n - m; j ++) if(dp[i + 1][j]) {
for(int k = 0; k <= (m + 1) / 2 && (1 << i) * k <= j; k += 2) {
(dp[i][j - (1 << i) * k] += dp[i + 1][j] * C((m + 1) / 2, k) % mod) %= mod;
}
}
}
ll ans = 0;
for(int i = 0; i <= n - m; i ++) (ans += dp[0][i] * calc(i, m + 1 - (m + 1) / 2) % mod) %= mod;
ans = (C(n, m) - ans + mod) % mod;
printf("%lld", ans);
return 0;
}