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;
}
posted @ 2022-02-16 15:34  lahlah  阅读(40)  评论(0编辑  收藏  举报