山东济南彤昌机械科技有限公司 山东济南江鹏工贸游有限公司

bzoj 2281 [Sdoi2011]黑白棋(博弈+组合计数)

 

 

黑白棋(game

【问题描述】

小A和小B又想到了一个新的游戏。

这个游戏是在一个1*n的棋盘上进行的,棋盘上有k个棋子,一半是黑色,一半是白色。

最左边是白色棋子,最右边是黑色棋子,相邻的棋子颜色不同。

 

小A可以移动白色棋子,小B可以移动黑色的棋子,他们每次操作可以移动1到d个棋子。

每当移动某一个棋子时,这个棋子不能跨越两边的棋子,当然也不可以出界。当谁不可以操作时,谁就失败了。

小A和小B轮流操作,现在小A先移动,有多少种初始棋子的布局会使他胜利呢?

【输入格式】

共一行,三个数,n,k,d。

【输出格式】

输出小A胜利的方案总数。答案对1000000007取模。

【样例输入】

10 4 2

【样例输出】

182

【数据规模和约定】

对于30%的数据,有 k=2。

对于100%的数据,有1<=d<=k<=n<=10000, k为偶数,k<=100。

 

【思路】

 

       博弈+组合计数

       将相邻黑白点看作是一堆石子,则问题转化为Nimk游戏,即有n堆石子每次可以在1~d堆中拿出任意不为0个数的石子,什么时候局面必胜。

       结论:当且仅当nim和中1的个数为d+1的倍数,有局面必败。每次最多只能使d个为0,先手不能转移到必败态 ,则后手可以通过操作获胜。

       补集转化,求先手必败的局面数。

       设f[i][j]表示nim和前i位中有j个的先手必败的方案数,枚举 d+1 的倍数转移:

              f[i][j+k*(d+1)*(1<<i)]+=f[i][j]*C(K/2,k*(d+1))

       则最后答案为C(n,K)-sigma{ f[15][i]*C(n-i-K/2,K/2) }

 

【代码】

 

 1 #include<cstdio>
 2 #include<iostream>
 3 using namespace std;
 4 
 5 typedef long long LL;
 6 const int N = 2*1e4+10;
 7 const int M = 2*1e2+10;
 8 const int MOD = 1e9+7;
 9 
10 LL c[N][M],f[M][N];
11 
12 void get_c() {
13     for(int i=0;i<N;i++) c[i][0]=1;
14     for(int i=1;i<N;i++) {
15         for(int j=1;j<=min(i,M-1);j++)
16             c[i][j]=(c[i-1][j-1]+c[i-1][j])%MOD;
17     }
18 }
19 LL C(int n,int k) {
20     if(k>n-k) k=n-k; return c[n][k];
21 }
22 
23 int n,K,D;
24 
25 int main() {
26     get_c();
27     scanf("%d%d%d",&n,&K,&D);
28     K>>=1;
29     f[0][0]=1;
30     for(int i=0;i<15;i++)
31         for(int j=0;j<=n-2*K;j++)
32             for(int k=0;k*(D+1)<=K && j+k*(D+1)*(1<<i)<=n-2*K;k++)
33                 f[i+1][j+k*(D+1)*(1<<i)]=(f[i+1][j+k*(D+1)*(1<<i)]+f[i][j]*C(K,k*(D+1))%MOD)%MOD;
34     LL ans=0;
35     for(int i=0;i<=n-2*K;i++) 
36         ans=(ans+f[15][i]*C(n-i-K,K)%MOD)%MOD;
37     printf("%lld",(C(n,2*K)-ans+MOD)%MOD);
38     return 0;
39 }

 

posted on 2016-02-26 09:28  hahalidaxin  阅读(528)  评论(1编辑  收藏  举报