[BZOJ2281][Sdoi2011]黑白棋(博弈+组合数学)

=== ===

这里放传送门

=== ===

题解

这题当时ATP手玩了好久才找到一点规律。。现在想想当时历尽艰险的手玩过程还出一身冷汗Σ( ° △ °|||)︴。。
比较容易发现的一个事情就是A肯定总想把棋子往右怼,B肯定总想把棋子往左怼。。但是有些时候A的某些棋子是怼不动的,因为可能它右边紧挨着有一个黑的棋子堵着它。对于A来说的话它往左走是没意义的,因为它往左走不能达到怼B的效果反而会促进B来怼它(这什么烂解释(ノ`Д)ノ),也就是不能改变必败态而反倒会延缓它面临必胜态的获胜时间这种感觉。。。

那么考虑一个白色棋子,如果它右边紧挨着有一个黑棋子,那么称它为“不可活动的”,同样,如果一个黑棋子左边紧挨着有一个白棋子,那么它也是“不可活动的”。显然当轮到某个人的时候如果它没有可以活动的棋子了它就输了。
那么它们采用什么策略才能达到尽量怼别人而又不被别人怼的效果呢?首先有一种不是那么好的策略就是每次每个人都尽量把操作的棋子怼到最边上,那这样的话每次移动都可以消掉1..d个活动棋子。这就可以联系到巴什博弈问题了。如果这是对的的话那只要活动棋子的数目可以被d+1整除就是A的必败态,那么只需要枚举活动棋子的数目就可以用排列组合来做了?
但是很遗憾这并不对。。因为它必胜还是必败不仅跟活动棋子的数目有关,还跟活动棋子中间的空格个数有关。比如说这一对活动棋子中间有很多空格,那么A可以只往右移动一点点,让它还是一对活动棋子,然后把必败态推给B。同样B也可以反推给A。但是这种推来推去的过程是不能无限进行下去的,总有一个时刻中间空格就不够了。

这样的话如果把一个白子和它右边的黑子看成一个整体,中间的空格数目看成是石子个数,那么问题就变成了有k/2堆石子,每次每个人可以选择1..d堆石子从里面拿走任意个数的石子,不能操作的人负。这看起来像是Nim游戏和巴什博弈的结合版。。这玩意儿怎么搞呢?
仍然先考虑简化版本,如果d=1,也就是每次只能选一堆石子,那这个东西就是一个普通的Nim游戏,那就是考察所有石子的异或值是否为0了。
那一看异或运算嘛那就看看能不能把每一位分开考虑。对于每一位来说,如果把N堆石子的这一位都挑出来摆在这里,假设一共有x个1,那就相当于有一堆x个石子。当d=1的时候就是每次可以拿走一个或者不拿,那肯定就是当x是偶数的时候先手必败。推广到d更大的时候,按照每一位分开考虑的思路那这就又是一个巴什博弈问题。

那就考虑构造必败态,N堆石子按位考虑,每一位的1的数目只要保证是d+1的倍数就是A的必败态。用f[i][j]表示当前已经用了i个石子,考虑到第j位,必败态的方案数目,那这就可以递推了。每次到这一位的时候选一些出来放1其它放0,然后因为这个方案不但跟放了几个1有关跟每个1放到哪儿了也有关,所以再乘上一个组合数C(N,x)。
最后的时候因为有一些空格选择当做“石子”,但是有一些空格选择了放在两组之间,它们是没有意义的,但它们也会造成方案不同,所以最后统计答案的时候枚举有多少空格没有当做“石子”然后搞个插板就可以了。

md我当时是怎么想出来这种东西的。。真是佩服自己的脑回路。。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
long long Mod=1000000007;
int n,K,d;
long long mul[20010],ans,f[10010][20];
long long powww(long long a,int t){
    long long ans=1;a%=Mod;
    while (t!=0){
        if (t&1) ans=(ans*a)%Mod;
        a=(a*a)%Mod;t>>=1;
    }
    return ans;
}
long long C(int N,int M){
    if (M>N) return 0;
    long long tmp=mul[M]*mul[N-M]%Mod;
    tmp=powww(tmp,Mod-2)*mul[N]%Mod;
    return tmp;
}
long long Put(int N,int M){return C(N+M-1,M-1);}
int main()
{
    scanf("%d%d%d",&n,&K,&d);
    mul[0]=1;K/=2;n-=2*K;
    for (int i=1;i<=2*n;i++)
      mul[i]=mul[i-1]*i%Mod;
    f[0][0]=1;
    for (int i=0;i<=n;i++)
      for (int j=0;j<=16;j++)
        if (f[i][j]!=0){
            long long now=f[i][j];
            int w=(1<<j);//计算这一位的1的权值
            for (int k=0;k<=K;k+=d+1){
                int nxt=i+w*k;
                if (nxt>n) break;//如果超过了上限就不要计算了
                f[nxt][j+1]=(f[nxt][j+1]+now*C(K,k)%Mod)%Mod;
            }
        }
    for (int i=0;i<=n;i++){
        long long tmp=f[i][16];
        tmp=tmp*Put(n-i,K+1)%Mod;
        ans=(ans+tmp)%Mod;
    } 
    ans=Put(n,2*K+1)-ans;
    ans=(ans+Mod)%Mod;
    printf("%I64d\n",ans);
    return 0;  
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值