[JZOJ2368]. 【SDOI2011】黑白棋

题目描述

A和小B又想到了一个新的游戏。
这个游戏是在一个1*n的棋盘上进行的,棋盘上有k个棋子,一半是黑色,一半是白色。
最左边是白色棋子,最右边是黑色棋子,相邻的棋子颜色不同。
这里写图片描述
小A可以移动白色棋子,小B可以移动黑色的棋子,他们每次操作可以移动1到d个棋子。
每当移动某一个棋子时,这个棋子不能跨越两边的棋子,当然也不可以出界。当谁不可以操作时,谁就失败了。
小A和小B轮流操作,现在小A先移动,有多少种初始棋子的布局会使他胜利呢?
答案对1000000007取模。
对于30%的数据,有 k=2。
对于100%的数据,有1<=d<=k<=n<=10000, k为偶数,k<=100。

分析

我们来考虑一下30分:很显然发现如果两个棋子黏在一起,A必输,因为他只能往右移,B可以模仿他的操作。而分开来的话就很明显是A必赢。那么只要用全部方案减去黏在一起的方案就好了。
我们把这个发现往经典的石子游戏上面靠。我们发现A往右移,B往左移是绝对劣的做法,那么A,B只能不断往中间靠。那么中间的格子就在不断减少。相当于一堆石子两个人取。
那么原来的博弈就变成了经典的nim-K游戏,每人每次可以操作一到d堆。结论是:初始局面,K堆石子的石子数的二进制数进行d+1进制下的不进位加法,如果为0,则先手必败。证明的话可以上网查资料。
那么现在我们就可以一个一个二进制位来考虑以分配方案了,注意原问题的石子堆数应该是K/2。我们把不合法的算出来,总数减去就是了。
设f[i][j]表示考虑了前0到i-1位二进制,石子(空位)用去j个的方案数。
f[i+1][j+cnt(d+1)(1<<i)]+=f[i][j]C(cnt(d+1),K/2)
cnt是枚举的,后面那个C是组合数,就是看看哪些位置的二进制变成1.
最后弄出的方案,可能还有一些空位没有用,这些空位让原本方案可以随便平移,那么再对每一种用去空位不同的方案再乘上组合数,即可求出所有不合法方案,再用 CKn 去减就行了。

代码

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
typedef long long ll;
typedef double db;
const int mo=1e9+7,N=1e4+5;
int ans,fac[N],rev[N],f[20][N],n,m,K,d,i,j,k,Log,add;
int ksm(int x,int y)
{
    int ret=1;
    while (y)
    {
        if (y&1) ret=1ll*ret*x%mo;
        x=1ll*x*x%mo;
        y>>=1;
    }
    return ret;
}
void predo()
{
    fac[0]=1;
    fo(i,1,10000) fac[i]=1ll*fac[i-1]*i%mo;
    rev[10000]=ksm(fac[10000],mo-2);
    fd(i,10000,1) rev[i-1]=1ll*rev[i]*i%mo;
}
int c(int n,int m)
{
    return 1ll*fac[m]*rev[n]%mo*rev[m-n]%mo;
}
int main()
{
    scanf("%d %d %d",&n,&K,&d);
    m=n-K;
    predo();
    f[0][0]=1;
    Log=trunc(log(m)/log(2));
    fo(i,0,Log)
        fo(j,0,m)
        if (f[i][j])
        {
            fo(k,0,n)
            {
                add=(1<<i)*k*(d+1);
                if (j+add>n-K||k*(d+1)>K/2) break;
                f[i+1][j+add]=(f[i+1][j+add]+1ll*f[i][j]*c(k*(d+1),K/2))%mo;
            }
        }
    fo(j,0,m)
        ans=(ans+1ll*f[i][j]*c(K/2,m-j+K/2))%mo;
    ans=(c(K,n)-ans+mo)%mo;
    printf("%d\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值