2281: [Sdoi2011]黑白棋
Time Limit: 3 Sec Memory Limit: 512 MB
Submit: 592 Solved: 362
[Submit][Status][Discuss]
Description
Input
Output
输出小A胜利的方案总数。答案对1000000007取模。
Sample Input
Sample Output
HINT
1<=d<=k<=n<=10000, k为偶数,k<=100。
Source
感受到了被博弈论支配的恐惧……
上来将题意理解为了白棋统一向右走,黑棋统一向左走,只要移动黑白棋就会碰到一起,然后就成了一个裸的nim博弈,然后就没有然后了……
后来看黄学长的题解领悟到正确思路后默默的跪膜……
首先我们先确认一下游戏结束的条件,如果A失败就是所有黑白棋都挨在一起且都挤在左侧,反之则都挤在右侧,原理显然。
那么我们可以发现,如果我们想胜利白棋一定不会向左移,黑棋一定不会向右移,所以,我们可以发现当每一个白棋都和他左边的黑棋碰到一起时游戏实际上就已经结束了。所以,我们可以将相邻的两个棋子配成k/2对,目标实际就转化为了有k/2堆石子一次最少取一个堆里的任意颗石子,最多取d堆里的任意石子。谁先无法行动谁就失败。这就是传说中的nimk模型。(lc:只要你多看几眼论文就会发现这些奇奇怪怪的名词)
对于nimk模型,先手必败当且仅当对于二进制每一位,如果所有堆的石子数二进制这一位1的和都能被(d+1)整除。
证明:
这种“谁不能移谁就输”的游戏先手必败就是当后手可以以某种对称的方式使你每走一步他都可以走一步实质完全一样的与你对应。表现在这道题里就是当你在二进制里的第i位取x个1时,他只要取d+1-x个就好了。
不过这道题我们并不是要去判断是否合法, 而是计算方案数。
为了方便,我们可以通过融斥去求答案。我们设f[i][j]为考虑了所有堆二进制前i位,已经在堆里面一共放了j个石子的方案数。转移方程即为:f[i+1][j+k*(d+1)*(1<<i)]+=f[i][j]*C(K/2,k*(d+1))。这里的K我指的是题目中的k,k*(d+1)就是枚举一共放多少组,C(K/2,k*(d+1))代表究竟放到哪一堆里。最后累加的时候记得再算上我们剩余的石子的摆放数,最终拿C(n,k)减去不合法方案数即可。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <algorithm>
#define N 10005
using namespace std;
int n,m,d,p=;
long long ksm(long long a,long long z)
{
long long ans=;
while(z)
{
if(z&) ans*=a,ans%=p;
a*=a;
a%=p;
z>>=;
}
return ans;
}
long long jc[N],ni[N];
long long get_c(int x,int y)
{
return ((jc[x]*ni[y]%p)*ni[x-y]%p)%p;
}
long long f[][N];
int main()
{
scanf("%d%d%d",&n,&m,&d);
if(m==)
{
printf("%lld\n",1ll*(n-)*(n-)/);
exit();
}
jc[]=ni[]=;
for(int i=;i<=n;i++) jc[i]=(jc[i-]*i)%p;
ni[n]=ksm(jc[n],p-);
for(int i=n-;i;i--) ni[i]=ni[i+]*(i+)%p;
f[][]=;
for(int i=;i<=;i++)
{
for(int j=;j<=n-m;j++)
{
for(int k=;j+(d+)*k*(<<i)<=n-m&&(d+)*k<=m/;k++)
{
f[i+][j+(d+)*k*(<<i)]+=f[i][j]*get_c(m/,k*(d+));
f[i+][j+(d+)*k*(<<i)]%=p;
}
}
}
long long ans=;
for(int i=;i<=n-m;i++)
ans+=f[][i]*get_c(n-i-m/,m/)%p,ans%=p;
printf("%lld\n",(get_c(n,m)-ans+p)%p);
return ;
}