【SDOI201】黑白棋 _【XSY3064】小奇的博弈(博弈,nim,dp,组合数)

显然,如果白棋往左,黑棋往右,最后肯定会两两碰在一起,就像这样:

在这里插入图片描述

红框框起来的是会碰在一起的棋子。(我们把会碰到一起的棋子称为一对棋子)

如下图就是碰在一起的一种情况:

在这里插入图片描述

那么现在假设是 \(A\) 遇到了这种情况,那么无论他操作的是白棋或黑棋,他肯定会输。因为另一个人可以操控棋子跟着 \(A\) 的棋子走,一直保持棋子两两紧逼的状态,直到所有棋子都堆在一边,这时 \(A\) 就无路可走了,失败。

不妨设开始前每对棋子之间的距离为 \(a_1,a_2,\dots,a_{\frac{k}{2}}\),那么原题就可以转化成一个 k-nim 游戏:现在有 \(\frac{k}{2}\) 堆石子,第 \(i\) 堆石子的石子个数是 \(a_i\),现在两人轮流进行如下操作:在这之中任意选取 \(1\sim d\) 堆石子,对于选取的这几堆石子中的每堆石子,能拿走任意正整数个石子。最后不能进行操作为失败。

这种问题有一个结论:将当前状态的 \(a_i\) 用二进制表示,设 \(s_i\) 表示 \(a_1\sim a_{\frac{k}{2}}\) 中二进制下第 \(i\)\(1\) 的个数,\(s_i\equiv s_i'\pmod {k+1}\)。若所有的 \(s_i'\) 都为 \(0\),则这个状态为必败状态,否则为必胜状态。

证明:

  1. \(a_i\) 全部为 \(0\) 的状态是必败状态,且此时所有的 \(s_i'\) 都为 \(0\)

  2. 设状态 \(P\)\(s_i'\) 全部为 \(0\) 的状态,状态 \(N\) 是存在 \(s_i'\) 不为 \(0\) 的状态。

    假设当前状态为 \(P\) 状态。

    显然,由于我至少要在一个堆里面取石子,所以某些 \(a_i\) 的一定会改变。所以首先,某些 \(s_i\) 一定会改变。然后又由于一次最多只能改变 \(d\) 个堆,所以对于每一个 \(s_i\),最多只会加上或减去 \(d\)。又由于一开始 \(s_i\equiv0\pmod{d+1}\),所以变化后的 \(s_i\) 一定不可能满足 \(s_i\equiv0\pmod{d+1}\)

    也就是说,对于任意的 \(P\) 状态,一定只能转移成 \(N\) 状态。

  3. 假设当前状态为 \(N\) 状态,那么我们要证明的目标是:对于任意的 \(N\) 状态,一定存在一种转移方式转移成 \(P\) 状态。

    我们从高位到低位地考虑所有的二进制位。显然,最高位的石子我们要全部取走。假设当前位为第 \(i\) 位,且已经满足了 \(s_j'=0\)\(j>i\)),并且改变了 \(m\) 个堆的石子数量(\(m\leq d\))。

    那么我们现在就是要证明:是否有一种操作,能使得 \(s_i'=0\),且新改变的堆加上以前改变过的堆的总堆数 \(m'\leq d\)。(类似数学归纳法)

    有一个比较显然的性质:对于那些已经改变的 \(m\) 堆,我改变这些堆的低位不会影响到高位的 \(s_j'\)

    设在这 \(m\) 堆中,每一堆所含石子个数的第 \(i\) 位上共有 \(a\)\(1\)\(b\)\(0\)

    分情况讨论:

    1. \(a\geq s_i'\)。那我们就可以在这 \(m\) 堆中找到 \(s_i'\) 个第 \(i\) 位为 \(1\) 的堆并取走这一位所代表的石子(也就是 \(2^i\) 个石子),显然根据我们刚刚提到的性质,取走这些石子不会影响高位的 \(s_j'\)。(也就是让 \(s_i'\gets 0\)

    2. \(b\geq (d+1)-s_i'\)。那我们就可以在这 \(m\) 堆中找到 \((d+1)-s_i'\) 个第 \(i\) 位为 \(0\) 的堆并加上这一位所代表的石子(也就是 \(2^i\) 个石子)(就算加上了这些石子,对于这一堆来说,石子总数还是减的,因为我们一开始减去了最高位所代表的石子),显然根据我们刚刚提到的性质,取走这些石子不会影响高位的 \(s_j'\)。(也就是让 \(s_i'\gets d+1\)

    3. 上述两种情况都不满足,也就是说 \(a< s_i'\),那么我们先取走这 \(a\) 堆中第 \(i\) 位的石子,然后再从这 \(m\) 堆之外的堆里面找 \(s_i'-a\) 个第 \(i\) 位为 \(1\) 的堆并取出它第 \(i\) 位的石子,总堆数变成了 \(m+s_i'-a=b+s_i'\leq d+1-s_i'+s_i'=d+1\),所以这种取法是合法的。

    综上所述,我们可以保证对于任意的 \(N\) 状态,一定存在一种转移方式转移成 \(P\) 状态。

综上所述,我们就可以证明 \(P\) 状态是必败状态,\(N\) 状态是必胜状态,结论成立。

接下来就是如何计算方案。

\(dp(i,j)\) 表示前 \(i-1\) 位每一位的异或和均为 \(0\),已经有 \(j\) 个石子的方案数。(也就是必败方案数)

枚举 \(a_1\sim a_\frac{k}{2}\) 的第 \(i\) 位共有多少个 \(1\),且满足个数是 \((d+1)\) 的倍数(也就是枚举 \((d+1)\)\(x\) 倍)。

然后在 \(\frac{k}{2}\) 个堆里面取 \(x(d+1)\) 个,把他们的第 \(i\) 位设为 \(1\),总方案数是 \(\dbinom{\frac{k}{2}}{x(d+1)}\)

最后我们还要枚举堆在哪里,也就是 \(\dbinom{n-j-\frac{k}{2}}{\frac{k}{2}}\)

然后 dp 一下就好了。

代码如下:

#include<bits/stdc++.h>

#define LN 20
#define K 210
#define N 10010
#define ll long long
#define int long long
#define mod 1000000007

using namespace std;

int n,k,d;
int C[N][K],dp[LN][N];

signed main()
{
	scanf("%lld%lld%lld",&n,&k,&d);
	C[0][0]=1;
	for(int i=1;i<=n;i++)
	{
		C[i][0]=1;
		for(int j=1;j<=200;j++)
			C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
	}
	dp[0][0]=1;
	for(int i=0;i<=16;i++)
	{
		ll t=(1ll<<i);
		for(int j=0;j<=n-k;j++)
			for(int x=0;t*x*(d+1)<=n-k&&x*(d+1)<=k/2;x++)
				dp[i+1][j+t*x*(d+1)]=(dp[i+1][j+t*x*(d+1)]+(1ll*dp[i][j]*C[k/2][x*(d+1)])%mod)%mod;
	}
	ll ans=0;
	for(int i=0;i<=n-k;i++)
		ans=(ans+1ll*dp[17][i]*C[n-i-k/2][k/2]%mod)%mod;
	printf("%lld\n",((C[n][k]-ans)%mod+mod)%mod);
	return 0;
}
posted @ 2022-10-29 11:23  ez_lcw  阅读(30)  评论(0编辑  收藏  举报