正题
题目链接:https://www.luogu.com.cn/problem/P5363
题目大意
(1 imes n)的网格上有(m)个硬币,两个人轮流向前移动一个硬币但是不能超过前一个硬币,无法移动者输。
求有多少种情况先手必胜。
解题思路
竟然有我会的题,我感动
位置做差分再减去(1)之后就是一个经典的阶梯博弈问题了,结论就是奇数位置的异或和。
但是这题是计数,先让(n)减去(m),然后正难则反考虑求总方案和后手必胜的情况,这样问题就变为有多少个长度为(m)的非负整数序列满足它们的和不超过(n)且奇数位置的异或和为(0)。
考虑枚举奇数位置的和,奇数位置个数为(z=lfloorfrac{m+1}{2} floor),设(f_i)表示(z)个数的和为(i)时异或和为(0)的方案数,这个状态直接计算起来很难搞。
可以枚举每一个位的(1)的数量,显然每一个位的(1)数量肯是偶数。然后用组合数转移即可。
然后设(g_i)表示(m-z)个数和不超过(i)的方案数,那么有(g_i=sum_{j=0}^iinom{j+m-z-1}{m-z-1}),前缀和转移就好了。
然后答案就是(inom{n+m}{m}-sum_{i=0}^nf_ig_{n-i})(注意这里的(n)已经减去(m)了),因为模数不是质数直接杨辉三角求就好了。
时间复杂度(O(nmlog m)),当然肯定是跑不满的
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=2e5,P=1e9+9;
ll n,m,ans,c[N][51],f[N],g[N];
signed main()
{
scanf("%lld%lld",&n,&m);ans=0;
if(n<=m)return puts("0")&0;
c[0][0]=1;
for(ll i=1;i<=n;i++)
for(ll j=0;j<=min(i,m);j++)
c[i][j]=((j?c[i-1][j-1]:0)+c[i-1][j])%P;
n-=m;ll z=(m+1)/2;
f[0]=1;
for(ll i=1;i<=18;i++)
for(ll j=n;j>=0;j--)
for(ll k=1;k<=z/2;k++){
if(j<(k*(1<<i)))break;
(f[j]+=f[j-k*(1<<i)]*c[z][2*k]%P)%=P;
}
for(ll i=0;i<=n;i++)
g[i]=(g[i-1]+c[i+m-z-1][m-z-1])%P;
for(ll i=0;i<=n;i++)
(ans+=f[i]*g[n-i]%P)%=P;
printf("%lld
",(c[n+m][m]-ans+P)%P);
return 0;
}