洛谷 P5363 [SDOI2019]移动金币

Alice和Bob将要进行如下的一场游戏。二人轮流操作,且Alice先行。
当轮到一个玩家的时候,他可以选择一枚金币,并将其向左移动任意多格,且至少移动一格。
金币不能被移出棋盘,也不能越过其它金币。

一个 \(1\times n\) 的棋盘上最初摆放有 \(m\) 枚金币。其中每一枚金币占据了一个独立的格子,任意一个格子内最多只有一枚金币。

如果轮到一个玩家的时候他已经无法做出任何有效操作了(显然这个时候\(m\)枚金币恰好落在最左侧的\(m\)个格子中),则被判定为输家。已经知道Alice和Bob都是极致聪明的人,他们在任何局面下总能做出最优的操作。那么有多少初始状态能保证Alice必胜呢?

首先引入阶梯nim的概念,有\(n\)堆石子,从\(0…n-1\)标号,每次只能将第\(i\)堆石子若干个移动到第\(i-1\)堆石子。

注意到如果我们移动下标为偶数的石子,那么下一步对方一定可以模仿你把这堆石子再移动到下标为偶数上,所以可以把下标为偶数的石子忽略,而移动下标为奇数的石子可以看作把这堆石子移动到忽略的状态,也就是取走,所以阶梯nim相当于对下标为奇数的石子做nim游戏。

而回到这个题,我们可以把\(m\)个金币分成的\(m+1\)个距离看作\(m+1\)堆石子,石子总数恰好是\(n-m\),那么这个相当于从右往左的阶梯nim游戏,那么下标为奇数的位置有\(\frac{m+1}{2}\)个。

我们不妨设\(m_1=\frac{m+1}{2},m_2=m+1-m_1\),分别表示下标为奇数和偶数的个数。

想知道必胜态的方案数,就必须要知道奇数石子异或和不为\(0\)的方案数,这个不好求,所以我们可以先求异或和为\(0\)的方案数,然后用总方案数减去异或和为\(0\)的方案数。

考虑到异或的性质,我们可以对每一位进行考虑,设\(f_{i,j}\)表示从低到高考虑了\(i\)位,选了\(j\)个石子的方案数,考虑枚举这一位填多少个\(1\),那么奇数位置必须填偶数个\(1\),偶数位置可以随便填。

所以我们可以提前预处理出\(c_i\)表示填\(i\)\(1\)的方案数,考虑枚举填奇数位置的个数,所以有如下式子:

\[c_i=\sum_{j=0}^i\begin{pmatrix}m_1\\j\end{pmatrix}\begin{pmatrix}m_2\\i-j\end{pmatrix}[j\ mod\ 2==0] \]

然后枚举\(x\)表示当前位置填多少个\(1\),可以写出dp式子:

\[f_{i+1,j+x2^i}+=f_{i,j}\times c_x \]

最后答案就是\(\begin{pmatrix}n\\m\end{pmatrix}-f_{18,n-m}\)

还有\(n<m\)无解。

Code

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
const int N = 2e5;
const int p = 1e9 + 9;
using namespace std;
int n,m,f[19][N + 5],fac[N + 5],inv[N + 5],ans,c[N + 5];
int C(int n,int m)
{
    if (n < 0 || m < 0 || m > n)
        return 0;
    return 1ll * fac[n] * inv[m] % p * inv[n - m] % p;
}
int Cc(int n,int m)
{
    if (n == 0 && m == 0)
        return 1;
    return C(n + m - 1,n - 1);
}
int main()
{
    scanf("%d%d",&n,&m);
    if (m > n)
    {
        cout<<"0"<<endl;
        return 0;
    }
    fac[0] = inv[1] = 1;
    for (int i = 1;i <= N;i++)
        fac[i] = 1ll * fac[i - 1] * i % p;
    for (int i = 2;i <= N;i++)
        inv[i] = 1ll * (p - p / i) * inv[p % i] % p;
    inv[0] = 1;
    for (int i = 1;i <= N;i++)
        inv[i] = 1ll * inv[i - 1] * inv[i] % p;
    f[0][0] = 1;
    for (int i = 0;i <= m + 1;i++)
        for (int j = 0;j <= i;j += 2)
            c[i] += 1ll * C((m + 1) / 2,j) * C(m + 1 - (m + 1) / 2,i - j) % p,c[i] %= p;
    for (int i = 0;i <= 17;i++)
        for (int j = 0;j <= n - m;j++)
            for (int x = 0;x * (1 << i) + j <= n - m && x <= m + 1;x++)
                f[i + 1][j + x * (1 << i)] += 1ll * f[i][j] * c[x] % p,f[i + 1][j + x * (1 << i)] %= p;
    ans = (C(n,m) - f[18][n - m]) % p;
    cout<<(ans + p) % p<<endl;
    return 0;
}
posted @ 2020-10-14 16:42  eee_hoho  阅读(162)  评论(0编辑  收藏  举报