[SDOI2019] 移动金币

[SDOI2019] 移动金币

今天也不知道怎么想的,

就开了一道往年的省选题,

这都是次要的,

主要是我看出来了是一道博弈论,

我也知道我不会博弈论,

但是我就是要搞.

然后就搞了一上午,

啥也没搞出来,

就去乖乖地学博弈论了qwq.

淦!

话说为什么昨天用繁体今天就用简体了?

谁知道呢!?~

好了,

言归正传.

这道题显然是一道博弈论(废话),

在我学了博弈论之后

我们很容易就可以发现这是一个阶梯Nim,

这里我们把向左移硬币看做是向右移格子,

这样就是一个阶梯Nim了.

在阶梯Nim中,

如果现在处于一个必胜的状态,

那么我们一定会移动奇数个的位置来使得接下来的状态变成必败状态.

下一步那个人一定会移动偶数个的位置来使得状态变回去,

这样我们的状态其实没有发生变化.

这样我们就可以把这个题看成用 \(m\) 个板子将 \(n - m\) 个格子分开,

可以空.

然后因为如果移动第奇数位的,

那我们就会从偶数位移到奇数位来模仿他的操作,

直到头上,

在博弈论中我们通常忽视这种可以模仿的状态,

所以奇数位没有用.

所以我们就只需要考虑偶数位,

这样我们就只需要计算偶数位的不合法的方案数,

然后用总方案数 - 不合法的.

这里我们可以利用一下异或的性质,

异或是按位进行的一种运算方式,

所以我们就只需要计算每一位的偶数个1的方案数.

所以我们就可以用dp了,

我们设 \(f[i][j]\) 表示考虑了前 \(i\) 位, 共放置了 \(j\) 个格子的方案数.

所以 \(f[i][j]=\sum_{k\%2=0}f[i+1][j-2^ik]C_{q}^k\) .

code:

#include <cstdio>
#include <iostream>
typedef long long ll;
int read(){
    int x = 0;
    char ch = getchar();
    while (!isdigit(ch)) ch = getchar();
    while (isdigit(ch)){
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    return x;
}
const int N = 1.5e5 + 5, M = 55, mod = 1e9 + 9;//模数莫写错,我刚才写成了1e9 + 7,调了半天qwq
ll f[M][N], fac[N], inv[N];//f[i][j]表示当前考虑了前i位,已经放了j个格子的不合法的方案数,fac[i]为i的阶乘,inv[i]为i的阶乘的逆元
ll ksm(ll a, int b){//快速幂
    ll ans = 1;
    while (b){
        if (b & 1) ans = ans * a % mod;
        a = a * a % mod;
        b  >>= 1;
    }
    return ans;
}
ll C(int n, int m){//计算组合数
    if (n < m || m < 0) return 0;
    return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
ll chaban(int n, int m){//插板计算奇数位置的方案数
    if (n == 0 && m == 0) return 1;
    return C(n + m - 1, m - 1);
}
void init(int n){//预处理出阶乘和逆元,便于取模和运算
    fac[0] = 1;
    for (int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i % mod;
    inv[n] = ksm(fac[n], mod - 2);
    for (int i = n - 1; i >= 0; i--) inv[i] = inv[i + 1] * (i + 1) % mod;
}
int main(){
    int n = read(), m = read();
    if (n < m){
        printf("0");
        return 0;
    }
    init(n + m);//预处理
    int k = 1;
    while ((1 << k) <= n) k++;//计算n的最高位
    f[k][n - m] = 1;//初始态方案数为1,因为第k位是最高位,2 ^ k > k,所以第k位不可能有1,所以只有一种方案,就是终态,也就是结束态
    for (int i = k - 1; i >= 0; i--){//枚举第i位,第k位无需枚举,原因↑
        for (int j = 0; j <= n - m; j++){//枚举已经放置了的格子数量
            if (!f[i + 1][j]) continue;//如果上一位都没有方案,那么这一位也死球了
            for (int k = 0; k <= ((m + 1) >> 1) && (k << i) <= j; k += 2){//这里有点类似完全背包的感觉,枚举当前位能放多少1
                f[i][j - (k << i)] = (f[i][j - (k << i)] + f[i + 1][j] * C(((m + 1) >> 1), k) % mod) % mod;
            }
        }
    }
    ll ans = 0;
    for (int i = 0; i <= n - m; i++) ans = (ans + f[0][i] * chaban(i, ((m + 2) >> 1))) % mod;//用在偶数位的方案数 * 在奇数位的方案数就是所有的不合法的方案数
    printf("%lld", (C(n, m) + mod - ans) % mod);
    return 0;
}

今天只卷了一道题.

淦!

posted @ 2021-08-22 16:31  sshadows  阅读(59)  评论(2编辑  收藏  举报