P2490 [SDOI2011] 黑白棋

题意

\(1\times n\) 的棋盘上有 \(k\) 个棋子(\(k\) 是偶数),旗子有两种颜色:黑、白。第 \(1\) 个是白色的,后面的每个棋子都和前面的棋子颜色不一样。先手能移动至多 \(d\) 个白棋,每个都可以向右移动任意步,但是不能跨过黑棋或移到棋盘外,后手能移动之多 \(d\) 个黑棋,规则和先手类似,不过祂只能向左移。
给出 \(n,k,d\),求有多少种初始局面,使得先手必胜。

思路

由于黑棋不能向右移动,白棋不能向左移动。那么可以把一个黑棋和祂左边的第一个白棋分为一组,每一组只保留两个棋之间的距离。
问题变成了有很多堆石子,每次可以选择至多 \(d\) 堆石子,从中任取一些(可以取完),取到最后一颗石子的人赢,问先手是否赢。
首先有个结论:对于二进制下的第 \(i\) 位,序列中二进制第 \(i\) 位是 \(1\) 的数的数量 \(mod\ (d+1)\) 不为 \(0\),那么先手必胜,否则后手必胜。
证明:

  1. 终结态是先手必败
  2. 对于任意先手必败态,后继状态都是先手必胜态。可以考虑被修改的最高位,祂的变化量一定小于 \(d+1\),所以一定是先手必胜态。
  3. 对于任意先手必胜态,存在后继为先手必败态。考虑最高的非 \(0\) 位,祂一定是可以变成 \(0\) 的,剩下的位是可以随便改变的,因为存在比祂们高的位变小了。

证毕。
那么后面就很简单了,设状态 \(f_{i,j}\) 表示考虑了二进制前 \(i\) 位都为 \(0\),使用了 \(j\) 个石子的方案。转移和统计答案是简单的。

代码

/*
Luogu P2490 [SDOI2011] 黑白棋
2026-04-08
*/
#include<bits/stdc++.h>
using namespace std;
namespace IO{
    template<typename T>
    inline void read(T&x){
        x=0;char c=getchar();bool f=0;
        while(!isdigit(c)) c=='-'?f=1:0,c=getchar();
        while(isdigit(c)) x=x*10+c-'0',c=getchar();
        f?x=-x:0;
    }
    template<typename T>
    inline void write(T x){
        if(x==0){putchar('0');return ;}
        x<0?x=-x,putchar('-'):0;short st[50],top=0;
        while(x) st[++top]=x%10,x/=10;
        while(top) putchar(st[top--]+'0');
    }
    inline void read(char&c){c=getchar();while(isspace(c)) c=getchar();}
    inline void write(char c){putchar(c);}
    inline void read(string&s){s.clear();char c;read(c);while(!isspace(c)&&~c) s+=c,c=getchar();}
    inline void write(string s){for(int i=0,len=s.size();i<len;i++) putchar(s[i]);}
    template<typename T>inline void write(T*x){while(*x) putchar(*(x++));}
    template<typename T,typename...T2> inline void read(T&x,T2&...y){read(x),read(y...);}
    template<typename T,typename...T2> inline void write(const T x,const T2...y){write(x),putchar(' '),write(y...),sizeof...(y)==1?putchar('\n'):0;}
}using namespace IO;
template<int mod>struct Modint{
    int z;
    Modint(){z=0;}
    Modint(int x){x%=mod;z=x<0?x+mod:x;}
    Modint(long long x){x%=mod;z=x<0?x+mod:x;}
    Modint(short x){x%=mod;z=x<0?x+mod:x;}
    Modint(char x){x%=mod;z=x<0?x+mod:x;}
    Modint(bool x){x%=mod;z=x<0?x+mod:x;}
    friend Modint operator+(Modint t,Modint t2){Modint ans;ans.z=(t.z+t2.z)%mod;return ans;}
    friend Modint operator*(Modint t,Modint t2){Modint ans;ans.z=1ll*t.z*t2.z%mod;return ans;}
    friend Modint operator-(Modint t,Modint t2){Modint ans;ans.z=(t.z-t2.z)%mod;return ans;}
    Modint operator<<(const int t)const{Modint ans;ans.z=(z<<t)%mod;return ans;}
    Modint operator>>(const int t)const{Modint ans;ans.z=(z>>t)%mod;return ans;}
    Modint&operator+=(const Modint t){z=(z+t.z)%mod;return *this;}
    Modint&operator*=(const Modint t){z=1ll*z*t.z%mod;return *this;}
    Modint&operator-=(const Modint t){z=(z-t.z)%mod;return *this;}
    Modint&operator<<=(const int t){z=(z<<t)%mod;return *this;}
    Modint&operator>>=(const int t){z=(z>>t)%mod;return *this;}
    Modint&operator++(){z++,z%=mod;return *this;}
    Modint&operator--(){z--,z%=mod;return *this;}
    Modint operator++(int){Modint ls=*this;z++,z%=mod;return ls;}
    Modint operator--(int){Modint ls=*this;z--,z%=mod;return ls;}
    friend Modint ksm(Modint a,int b){
        Modint ans=1;
        while(b){if(b&1) ans=ans*a;a=a*a,b>>=1;}
        return ans;
    }
    friend void read(Modint&z){
        int x=0;char c=getchar();bool f=0;
        while(!isdigit(c)) c=='-'?f=1:0,c=getchar();
        while(isdigit(c)) x=(x*10ll+c-'0')%mod,c=getchar();
        f?x=-x:0;
        z.z=x;
    }
    friend void write(Modint x){x.z<0?x.z+=mod:0;write(x.z);}
};
const int mod=1000000007,maxn=10010,maxk=110;
#define M Modint<mod>
int n,k,d;
M jc[maxn],inv[maxn],f[20][maxn];
M C(int n,int m){return jc[n]*inv[m]*inv[n-m];}
signed main(){
    read(n,k,d);
    jc[0]=1;
    for(int i=1;i<=n;i++) jc[i]=jc[i-1]*i;
    for(int i=0;i<=n;i++) inv[i]=ksm(jc[i],mod-2);
    f[0][0]=1;
    for(int i=0;i<=14;i++){
        int z=(1<<i);
        for(int j=0;j<=n-k;j++) for(int x=0;j+x*(d+1)*z<=n-k&&x*(d+1)<=k/2;x++) f[i+1][j+x*(d+1)*z]+=f[i][j]*C(k/2,x*(d+1));
    }
    M ans=0;
    for(int i=0;i<=n-k;i++) ans+=f[15][i]*C(n-i-k/2,k/2);
    write(C(n,k)-ans);
    return 0;
}
posted @ 2026-04-09 21:10  Link-Cut_Trees  阅读(14)  评论(0)    收藏  举报