题解 P2490 【[SDOI2011]黑白棋】
VinstaG173 · · 题解
感觉大家的题解的dp部分挺不错的了,但是k-nim部分还是不太好。这篇题解会主要讲解k-nim。
k-nim游戏是这样的:
有
这题的转化就是将相邻的白左黑右的棋子看作一堆石子,石子个数为两颗棋子之间的空格数。这样两个人移动一颗棋子都相当于从对应的堆中取出一些石子。
k-nim的解法是这样的:
首先将各堆石子数用二进制表示。
令
如果有任意一个
k-nim的解法证明:
敲黑板划重点啦!
如果所有的
如果有一些
但是有一个问题:如果这样选出来的石子堆数超过了
此时我们知道:如果一个数的高位从
于是我们每次只要尽量优先选择有高位变为
又因为无法操作的状态各个
接着的dp可以参见神仙们的题解,在此不予赘述。
Code:
#include<cstdio>
const int o=1e9+7;
int n,k,d,ans;
int frc[10007],inv[10007];
int dp[17][17007];
inline int qpw(int x,int v)
{
int r=1;
while(v)
{
(v&1)&&(r=1ll*r*x%o),x=1ll*x*x%o,v>>=1;
}
return r;
}
inline int C(int N,int M)
{
return 1ll*frc[N]*inv[M]%o*inv[N-M]%o;
}
int main()
{
scanf(" %d %d %d",&n,&k,&d);
frc[0]=1;
for(int i=1;i<=n;++i)
{
frc[i]=1ll*frc[i-1]*i%o;
}
inv[n]=qpw(frc[n],o-2);
for(int i=n;i;--i)
{
inv[i-1]=1ll*inv[i]*i%o;
}
dp[0][0]=1;
for(int i=0;i<=13;++i)
{
for(int j=0;j<=n-k;++j)
{
for(int x=0;k+(x<<i)<=n&&x<<1<=k;x+=d+1)
{
dp[i+1][j+(x<<i)]=(dp[i+1][j+(x<<i)]+1ll*dp[i][j]*C(k>>1,x))%o;
}
}
}
for(int i=0;i<=n-k;++i)
{
ans=(ans+1ll*dp[14][i]*C(n-i-(k>>1),k>>1))%o;
}
printf("%d\n",(C(n,k)-ans+o)%o);
return 0;
}