【bzoj2281】[Sdoi2011]黑白棋

博弈论---Nimk问题。 dp再搞搞。

很容易看出,该游戏的终态是每两个棋子都紧靠着。当一颗棋子移动,另一方与该棋子对应的那一刻可以立即追上,使得仍旧紧靠,最终棋子动弹不得,游戏结束。

还能看出,对于白色棋子(先手),往左走没有意义。因为黑子(后手)可以紧随其上使得两者距离不变。同理黑子只往左走。(黄学长貌似提出了反例?)

所以,问题可以抽象为Nim,与传统Nim只能选1堆不同,你可以选1-d堆。

这个拓展问题叫做Nimk问题。对于这种问题,我们可以证明:当将n堆棋子化为二进制,每一位上如果1的个数mod(k+1)==0 则为必败态。

详细证明,大传送术!http://blog.csdn.net/weixinding/article/details/7321139

最后只需要计算方案数。使用dp,dp[i][j]表示当前在二进制第i位上,总计用了j石头的方案。转移方程为:

dp[i+1][j+a*(k+1)*bin[i]]+=dp[i][j]*C(n,a*(k+1));

注意组合数处理,取%等细节即可。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define mo 1000000007
 4 #define N 10005
 5 #define LL long long
 6 LL c[N][205],dp[20][N],ans;
 7 int n,k,d,K,bin[20];
 8 void pre(){
 9     bin[0]=1; for(int i=1;i<=15;i++) bin[i]=bin[i-1]<<1;
10     for(int i=0;i<=n;i++) c[i][0]=1;
11     for(int i=1;i<=n;i++)
12     for(int j=1;j<=min(i,K);j++)
13     c[i][j]=(c[i-1][j]+c[i-1][j-1])%mo;
14 }
15 LL C(int n,int m){
16     if(n-m<m) m=n-m;
17     return c[n][m];
18 }
19 LL cal(LL& x,LL y){
20     x=(x+y)%mo;
21 }
22 int main(){
23     scanf("%d%d%d",&n,&K,&d); pre(); dp[0][0]=1;
24     for(int i=0;i<15;i++)
25     for(int j=0;j<=n-K;j++)
26     for(int k=0;k*(d+1)<=K/2 && j+k*(d+1)*bin[i]<=n-K;k++)
27     cal(dp[i+1][j+k*(d+1)*bin[i]],dp[i][j]*C(K/2,k*(d+1)));
28     for(int i=0;i<=n-K;i++)
29     cal(ans,dp[15][i]*C(n-i-K/2,K/2));
30     LL tot=C(n,K);
31     cout<<(tot-ans+mo)%mo;
32     return 0;
33 }
原文地址:https://www.cnblogs.com/enigma-aw/p/5953207.html