洛谷P2490 [SDOI2011]黑白棋(K-NIM)

开始渐渐理解博弈
首先提出一个结论:在最优策略下,白棋只会向右走,黑棋只会向左走

证明 :
若白棋向左走,那黑棋可以向左走相同的步数,但此时白棋的活动范围减少,显然会劣,黑棋同理

接下来考虑模型转换
把每一对棋子之间的点数看做石子数,那本题就是经典的 k-NIM 博弈
不妨解释一下 K-NIM 博弈

K-NIM 博弈

定义:共有 \(n\) 堆石子,每次可以从任意不超过 \(k\) 堆石子中拿走任意石子
在某次无法操作时,该人失败,游戏结束

先手必败:
当且仅当把每堆石子的数量转换成二进制后,每个二进制位上的和 %(k+1) 后均等于零
先手必胜:
只要不满足上述情况,均为先手必胜局面

解释一下为什么膜 \((k+1)\)
考虑多余的部分,对于 \([0,k]\)
若对面正处于必败局面,在对面操作后,更改的范围一定为 \([0,k]\)
那次者则可操作对应的步数 \([k,0]\) 使其重返必败局面
所以要膜 \((k+1)\)

证明:
首先所有堆石子数量均为0的状态一定为必败局面,此时显然满足先手必败
如果存在堆石子数量不为0,但满足先手必败局面,在对面操作完后,次者一定可以操作对应的石子数量使其重返必败局面
假设在修改完其中一堆石子后二进制最高位不为0的位数为m,那一定可以拿走对应的石子数量使其最高位变为0
因此满足先手必败,反之同理

转移就很好写了
\(dp_{i,j}\) 表示二进制前 \(i\) 位膜完后均为 \(0\) ,此时共选了 \(j\) 个石子的方案数
枚举哪几堆添加石子,则:

\[dp_{i+1,j+(1<<i)*x*(k+1)}+=dp_{i,j}\binom{\frac{m}{2}}{x*(k+1)} \]

因为每一对是无位置的,所以

\[ans+=dp_{maxi,j}\binom{n-i-\frac{m}{2}}{\frac{m}{2}} \]

\[ans=\binom{n}{k}-ans \]

Code
#include <bits/stdc++.h>
#define re register
#define int long long
#define pir make_pair
#define fr first 
#define sc second
#define db double
using namespace std;
const int mol=1e9+7;
const int maxn=1e7+10;
inline int qpow(int a,int b) { int ans=1; while(b) { if(b&1) (ans*=a)%=mol; (a*=a)%=mol; b>>=1; } return ans; }
inline int read() {
    int s=0,w=1; char ch=getchar();
    while(ch<'0'||ch>'9') { if(ch=='-') w=-1; ch=getchar(); }
    while(ch>='0'&&ch<='9') { s=s*10+ch-'0'; ch=getchar(); }
    return s*w;
}

int n,k,m,fac[10010],inv[10010];
inline int C(int n,int m) { if(n<m) return 0; return fac[n]*inv[m]%mol*inv[n-m]%mol; }
int f[22][100010];
signed main(void) {
    freopen("erp.in","r",stdin); freopen("erp.out","w",stdout);
    n=read(),k=read(),m=read();
    fac[0]=1; for(re int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mol; 
    inv[n]=qpow(fac[n],mol-2); for(re int i=n;i>=1;i--) inv[i-1]=inv[i]*i%mol;
    f[0][0]=1;
    for(re int i=0;i<=20;i++)
        for(re int j=0;j<=n;j++)
            for(re int x=0;j+(1<<i)*x*(m+1)<=n-k&&x*(m+1)<=k/2;x++) {
                (f[i+1][j+(1<<i)*x*(m+1)]+=f[i][j]*C(k/2,x*(m+1))%mol)%=mol;
            } 
    int ans=0;
    for(re int i=0;n-i-k/2>=0;i++) (ans+=f[21][i]*C(n-i-k/2,k/2)%mol)%=mol;
    printf("%lld\n",(C(n,k)-ans+mol)%mol);
}
posted @ 2021-11-03 09:35  zJx-Lm  阅读(203)  评论(0编辑  收藏  举报