bzoj 4550 小奇的博弈

$Nimk$ 游戏 + $dp$ 计数.

  • 最优策略下,白棋显然都选择右移,黑棋都选择左移.如果把每对棋子的间隔长度看做一堆石子的石子数目,那么此题就是一个 $Nim$ 游戏的变种,每次最多可以选 $d$ 堆石子进行操作.这个东西叫做 $Nimk$ 游戏.
  • $Nimk$ 游戏先手必败条件:对于每个二进制位 $i$ ,所有石子数目的第 $i$ 位之和为 $d+1$ 的倍数.
  • 即, $\forall i,(\sum_j(a_j>>i)\&1)\mod d+1=0​$ .
  • 转成 $Nimk$ 模型,有 $\frac k 2$ 堆石子,石子总数不超过 $n-k$ 个.求必胜方案数可以用总方案数 $n \choose k$ 减去必败方案数.
  • 令 $f(i,j)$ 表示从第 $0$ 位开始算,已经考虑了二进制的前 $i$ 位,用掉了 $j$ 个石子的方案数.转移有:

$$
f(i+1,j+x\cdot (d+1)\cdot 2^i)+=f(i,j)\cdot {k/2 \choose x\cdot(d+1)}
$$

  • 枚举 $x$ 进行转移,必败方案数就是 $\sum f(inf,j)\cdot {n-j-k/2\choose k/2}$ .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int read()
{
int out=0,fh=1;
char jp=getchar();
while ((jp>'9'||jp<'0')&&jp!='-')
jp=getchar();
if (jp=='-')
fh=-1,jp=getchar();
while (jp>='0'&&jp<='9')
out=out*10+jp-'0',jp=getchar();
return out*fh;
}
const int P=1e9+7;
const int add(int a,int b)
{
return (a+b>=P)?(a+b-P):(a+b);
}
inline int mul(int a,int b)
{
return 1LL * a * b % P;
}
inline void upd(int &a,int b)
{
a=add(a,b);
}
const int MAXK=101,MAXN=1e4+10;
int C[MAXN][MAXK],f[20][MAXN];
int main()
{
int n=read(),k=read(),d=read();
C[0][0]=1;
for(int i=1;i<=n;++i)
{
C[i][0]=1;
for(int j=1;j<=i && j<=k;++j)
C[i][j]=add(C[i-1][j],C[i-1][j-1]);
}
f[0][0]=1;
for(int i=0;i<=16;++i)
for(int j=0;j<=n-k;++j)
for(ll x=0;x*(d+1)<=k/2 && x*(d+1)*(1LL<<i)+j<=n-k;++x)
upd(f[i+1][x*(d+1)*(1<<i)+j],mul(f[i][j],C[k/2][x*(d+1)]));
int ans=0;
for(int j=0;j<=n-k;++j)
upd(ans,mul(f[16][j],C[n-j-k/2][k/2]));
ans=add(-ans,C[n][k]);
ans%=P,ans+=P,ans%=P;
cout<<ans<<endl;
return 0;
}