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

    题意:

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

    Alice 和 Bob 将要进行如下的一场游戏:二人轮流操作,且 Alice 先行。

    当轮到一个玩家的时候,他可以选择一枚金币,并将其向左移动任意多格,且至少移动一格。金币不能被移出棋盘,也不能越过其它金币。

    如果轮到一个玩家的时候他已经无法做出任何有效操作了(显然这个时候m枚金币恰好落在最左侧的m个格子中),则被判定为输家。

    已经知道 Alice 和 Bob 都是极致聪明的人,他们在任何局面下总能做出最优的操作。那么有多少初始状态能保证 Alice 必胜呢?

    $nleq 150000,mleq 50$。

    题解:

    将两个金币之间的距离视作物品,那么显然这是一个阶梯博弈,问题变为求出奇数阶梯异或和不为0的方案数。

    转换一下,相当于把$n-m$个东西分成$m/2$份使得$a_{1}igoplus a_{2}igopluscdotsigoplus a_{m/2}=0$。

    直接背包是$O(mn^{2})$的,但我们注意到二进制位之间互相不影响,所以可以考虑逐位背包。

    设$dp(i,j)$表示考虑二进制最高的i位,已经用了j个东西的方案数。转移时枚举第i位有多少a的二进制位为1。

    $dp(i,j)=sum limits_{k=0,k\%2=0}^{m/2}{C_{m/2}^{k} imes dp(i+1,j-k(1<<i))}$

    考虑完奇数阶梯后,偶数阶梯的位置已经定了,而阶梯上物品个数未定,于是利用插板法计算答案。

    $ans=sum limits_{i=0}^{n-m}{C_{i+m/2-1}^{m/2-1} imes dp(0,n-m-i)}$

    复杂度$O(nmlog{n})$。

    套路:

    • 博弈论常见模型:巴什博弈,尼姆博弈,阶梯博弈,反尼姆博弈。
    • 二进制位之间互不影响的计数$ ightarrow$按二进制位逐位dp。

    代码:

    #include<bits/stdc++.h>
    #define maxn 150005
    #define maxm 55
    #define inf 0x7fffffff
    #define mod 1000000009
    #define ll long long
    #define rint register int
    #define debug(x) cerr<<#x<<": "<<x<<endl
    #define fgx cerr<<"--------------"<<endl
    #define dgx cerr<<"=============="<<endl
    
    using namespace std;
    ll F[20][maxn],C[maxn][maxm];
    
    inline ll read(){
        ll x=0,f=1; char c=getchar();
        for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
        for(;isdigit(c);c=getchar()) x=x*10+c-'0';
        return x*f;
    }
    
    inline ll mo(ll x){return x>=mod?x-mod:x;}
    
    int main(){
        int n=read(),m=read(),od=(m+1)/2,ov=m-od+1;
        if(n<=m){printf("0
    ");return 0;} 
        int d=0; while((1<<d)<=n) d++; 
        F[d][0]=1,C[0][0]=1;
        for(rint i=1;i<=n;i++){
            C[i][0]=1;
            for(rint j=1;j<=min(i,m);j++) 
                C[i][j]=mo(C[i-1][j]+C[i-1][j-1]);
        }
        for(rint i=d-1;i>=0;i--)
            for(rint k=0;k<=od;k+=2)
                for(rint j=k*(1<<i);j<=n;j++)
                    F[i][j]=mo(F[i][j]+C[od][k]*F[i+1][j-k*(1<<i)]%mod);
        ll ans=0;
        for(rint i=0;i<=n-m;i++)
            ans=mo(ans+C[i+ov-1][ov-1]*F[0][n-m-i]%mod);
        printf("%lld
    ",mo(C[n][m]-ans+mod));
        return 0;
    }
    移动金币
  • 相关阅读:
    java基础之switch
    String的getBytes()方法
    Android adb命令
    shell中grep命令详解
    su root 和su
    adb shell 命令详解
    adb shell am 的用法
    adb logcat 基本用法
    Android、iOS和Windows Phone中的推送技术
    Android客户端消息推送原理简介
  • 原文地址:https://www.cnblogs.com/YSFAC/p/13099152.html
Copyright © 2011-2022 走看看