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) 编辑 收藏 举报