BZOJ 2281: [Sdoi2011]黑白棋 (Nim游戏+dp计数)

题意

这题目有一点问题,应该是在n个格子里有k个棋子,k是偶数.从左到右一白一黑间隔出现.有两个人不妨叫做小白和小黑.两个人轮流操作,每个人可以选 1~d 枚自己颜色的棋子,如果是白色则只能向右移动,如果是黑色只能向左移.移动过程中不能越过其他棋子.每个棋子的移动步数是任意的.不能操作的人就算输.求先手必胜的初始局面数模109+710^9+7的值.

分析

我们把k2frac k2每一对黑白棋之间的距离看作一堆石子,那么问题就转化为了有k2frac k2堆石子,每次可以选1...d1...d堆石子,每一堆随意取多少,没有石子取的算输.

我们把问题转换为总方案减去先手必败的方案.我们先考虑假设每一堆石子只有一个,那么必败状态是?没错,就是石子堆数%(d+1)=0的状态.因为先手不管拿1~d的多少,另一个人都可以拿若干石子使得两人拿的石子数加起来等于(d+1)
那么当每一堆石子不止一个时,把k2frac k2堆石子的石子数用二进制表示,统计每位上的1的个数,若每位上1的个数%(d+1)全为0,则必败.

所以说我们就可以DP了.用f[i][j]f[i][j]表示在二进制中满足了前ii位的1的个数%(d+1)都为0,石子数为jj的方案数.转移时只用枚举一下这一位上的1的个数是(d+1)的x倍就可以转移了.转移时乘上C(k2,x(d+1))C(frac k2,x*(d+1)),表示在k2frac k2堆石子中选哪些来放1.石子数确定了,但在格子上的位置还没有确定,最后还要乘上每个白棋放在哪里.

CODE

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
namespace READ {
	inline void read(int &num) {
		char ch; while((ch=getchar())<'0'||ch>'9');
		for(num=0;ch>='0'&&ch<='9';num=num*10+ch-'0',ch=getchar());
	}
}
using namespace READ;
typedef long long LL;
const int MAXN = 10005;
const int LOG = 15;
const int mod = 1e9+7;
int N, K, D;
LL f[LOG][MAXN], fac[MAXN], inv[MAXN];
inline void Pre() {
	fac[0] = fac[1] = inv[0] = inv[1] = 1;
	for(int i = 2; i <= N; ++i) inv[i] = 1ll * (mod - mod/i) * inv[mod%i] % mod;
	for(int i = 2; i <= N; ++i) fac[i] = fac[i-1] * i % mod, inv[i] = inv[i-1] * inv[i] % mod;
}
inline LL C(int n, int m) {
	if(m > n) return 0;
	return fac[n] * inv[m] % mod * inv[n-m] % mod;
}
int main () {
	read(N), read(K), read(D), Pre(); K>>=1;
	f[0][0] = 1;
	for(int i = 0, bit = 1; i < LOG-1; ++i, bit<<=1)
		for(int j = 0; j <= N-(K<<1); ++j) if(f[i][j])
			for(int x = 0; x*(D+1) <= K && j+x*(D+1)*bit <= N-(K<<1); ++x)
				f[i+1][j+x*(D+1)*bit] = (f[i+1][j+x*(D+1)*bit] + f[i][j] * C(K, x*(D+1))) % mod;
	LL ans = C(N, K<<1);
	for(int i = 0; i <= N-(K<<1); ++i) ans = (ans - f[LOG-1][i] * C(N-K-i, K)) % mod;
	printf("%lld
", (ans + mod) % mod);
}
原文地址:https://www.cnblogs.com/Orz-IE/p/12039400.html