【问题描述】
小A 和小B 又想到了一个新的游戏。
这个游戏是在一个1*n 的棋盘上进行的,棋盘上有k 个棋子,一半是黑色,
一半是白色。
最左边是白色棋子,最右边是黑色棋子,相邻的棋子颜色不同。
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。
这是一道博弈论问题,所以进行分析来得到一些有用的结论
经过思考,可以发现游戏的终止状态是1号白棋和1号黑棋紧靠着,2号白棋和2号黑棋紧靠着,即每对黑白棋都仅靠着
这个时候先手必败,无论移动哪些棋子,对手下一步只要将对应棋子与其移动一样的距离就又回到仅靠的状态,这样重复多次后终会无法移动
还可以得出一个结论,白棋只有往右移才有意义,黑棋只有往左移才有意义,如果白棋往左移,那么黑棋只要对应的棋子向左移相同的步数,对应棋子之间的距离并没有改变,即局面没有改变,所以面临必败的先手这样做也无法挽回败局,必胜的先手肯定不会这么做增加变数
那么问题就变为了,有n堆石子,每次可以从1~k堆中取任意数量的石子(不能不取),什么情况是必胜局面
这个问题叫做nimk游戏
Nimk
Nim游戏的简单扩展,不过证明我还是想了一些时间的。
n堆石子,每次从不超过k堆中取任意多个石子,最后不能取的人算失败。
把n堆石子的石子数用二进制表示,统计每一二进制位上的1的个数,若每一位上1的个数mod
(k + 1)全为0,则必败。否则必胜
证明:
1.显然终止局面全为0满足命题,为必败态
2.对于某个局面,若存在某些二进制位上的1的个数mod(k +1)不为0,则一定存在一个合法的移动,使得每一个二进制位上的1的个数mod(k + 1)等于0。设1的个数mod(k +1)不为0的最高二进制位上有m个1,则把这些1都变成0,记此时改变的堆数为m,若遇到下一个1的个数mod(k +1)不为0的二进制位上有r个1,设原来改变的m堆在这一二进制位上有a个1和b个0。若a >=r,则把其中r个1->0;若b >= k + 1 - r,则把其中k +1 - r个0->1;否则,有a < r且b< k + 1 - r,选择原来改变的m堆以外的r - a堆,这r-a堆在该位上是1,此时改变的堆数为a+ b + r - a = b + r < k + 1 - r + r = k +1,故为合法的移动。重复上述操作,必然能使得每一位上的1的个数mod (k + 1)都为0。
3.对于某个局面,若每一个二进制位上的1的个数mod(k +1)都为0,则一定不存在某个合法的移动,使得移动后每一二进制位上的1的个数mod (k +1)都为0。因为最多对k堆石子做改变,所以不可能在某一位改变k + 1个1或0,使得mod(k +1)仍为0,必然需要做偶数个0->1和偶数个1->0,对于1->0的这堆,必然存在高位,从1->0,要使高位的操作成立,又需要某一堆在这一位上0->1,依次类推,最后在最高位上存在一堆是0->1,这是不合法的。
证毕。
这时候可以用动态规划来求方案数
用f[i][j]来表示考虑前i位k/2堆石子堆中一共j颗石子的必败局面有多少种
对于一组i,j,枚举当前位加起来是d+1的多少倍即可进行转移
所以必胜的总方案数为c(k,n)-sig(c(k/2,n-k-j+k/2)*f[i][j])(0<=j<=n-k,i为最高位,c为排列组合)
program game;
const
maxn=1000000007;
var
n,k,d,i,j,q:longint;
ans:int64;
anti,quick:array [0..20001] of int64;
f:array [0..16,0..10001] of int64;
function min (a,b:int64):int64;inline;
begin
if a<b then exit(a)
else exit(b);
end;
function power (a:int64;b:longint):int64;inline;
var
i:int64;
begin
i:=1;
while b<>0 do
begin
if b and 1 = 1 then i:=i*a mod maxn;
b:=b div 2;
a:=a*a mod maxn;
end;
exit(i);
end;
function c (m,n:longint):int64;inline;
begin
exit(quick[n]*anti[m] mod maxn * anti[n-m] mod maxn);
end;
begin
assign(input,'game.in');
reset(input);
assign(output,'game.out');
rewrite(output);
read(n,k,d);
quick[0]:=1;
for i:=1 to 20000 do
quick[i]:=i*quick[i-1] mod maxn;
for i:=0 to 20000 do
anti[i]:=power(quick[i],maxn-2);
f[0][0]:=1;
for i:=1 to 15 do
for j:=0 to n-k do
for q:=0 to min(k div 2 div (d+1),j div ((1 shl (i-1))*(d+1))) do
f[i][j]:=(f[i][j]+c(q*(d+1),k div 2)*f[i-1][j-q*(1 shl (i-1))*(d+1)]) mod maxn;
ans:=0;
for i:=0 to n-k do
ans:=(ans+f[15][i]*c(k div 2,n-k-i+k div 2)) mod maxn;
writeln(((c(k,n)-ans) mod maxn + maxn) mod maxn);
close(input);
close(output);
end.