zoukankan      html  css  js  c++  java
  • 洛谷 P5363 [SDOI2019]移动金币

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

    一个 (1 imes 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}^iegin{pmatrix}m_1\jend{pmatrix}egin{pmatrix}m_2\i-jend{pmatrix}[j mod 2==0] ]

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

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

    最后答案就是(egin{pmatrix}n\mend{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;
    }
    
  • 相关阅读:
    CentOS中的环境变量配置文件
    SCVMM中Clone虚拟机失败显示Unsupported Cluster Configuration状态
    Windows Server 2012 虚拟化实战:SCVMM的安装和部署
    Windows Server 2012 虚拟化实战:网络(二)
    x86.zip
    音视频处理之PS封装的介绍与使用20180928
    界面编程之QT的数据库操作20180801
    界面编程之QT的线程20180731
    界面编程之QT的Socket通信20180730
    界面编程之QT的文件操作20180729
  • 原文地址:https://www.cnblogs.com/sdlang/p/13815712.html
Copyright © 2011-2022 走看看