【bzoj2281】 Sdoi2011—黑白棋

http://www.lydsy.com/JudgeOnline/problem.php?id=2281 (题目链接)

题意

  一个1*n的棋盘,棋盘上一个隔一个的放着个黑棋和白棋,最左端是白棋,最右端是黑棋每次可以向左或向右移动<=d颗棋子,移动不能跨越棋子,也不能越出边界,问先手必胜的初始状态有多少。

Solution

  Knim。

  右转介绍:http://blog.csdn.net/weixinding/article/details/7321139

  左转题解:http://blog.csdn.net/lych_cys/article/details/50184249

细节

  直接预处理10000的组合数会炸空间,所以我们采用公式${C^m_n=C^{n-m}_n}$进行转换。

代码

// bzoj2281
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>
#define LL long long
#define inf 2147483640
#define MOD 1000000007
#define Pi acos(-1.0)
#define free(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout);
using namespace std;

const int maxn=10010;
LL C[maxn][200],f[20][maxn],bin[20];
int n,K,d;

LL c(LL n,LL m) {
	if (m>n-m) return C[n][n-m];
	else return C[n][m];
}
int main() {
	bin[0]=1;for (int i=1;i<=15;i++) bin[i]=bin[i-1]<<1;
	scanf("%d%d%d",&n,&K,&d);
	for (int i=0;i<=n;i++) C[i][0]=1;
	for (int i=1;i<=n;i++)
		for (int j=1;j<=min(K,i);j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;   //预处理
	f[0][0]=1;
	for (int i=0;i<15;i++)
		for (int j=0;j<=n-K;j++)
			for (int k=0;k*(d+1)<=K/2 && j+k*(d+1)*bin[i]<=n-K;k++)
				f[i+1][j+k*(d+1)*bin[i]]=(f[i+1][j+k*(d+1)*bin[i]]+f[i][j]*c(K/2,k*(d+1)))%MOD;   //dp求解f[i][j],表示二进制前i位放j个石子
	LL ans=0;
	for (int i=0;i<=n-K;i++)   //枚举左右端点之间的长度(也就是放几个石子)
		ans=(ans+f[15][i]*c(n-i-K/2,K/2))%MOD;   //石子的总排列数*每一堆石子放置的位置数
	printf("%lld",(c(n,K)-ans+MOD)%MOD);
	return 0;
}

  

  

posted @ 2016-12-25 15:42  MashiroSky  阅读(293)  评论(0编辑  收藏  举报