P5363-[SDOI2019]移动金币【阶梯博弈,dp,组合数学】

正题

题目链接:https://www.luogu.com.cn/problem/P5363


题目大意

\(1\times n\)的网格上有\(m\)个硬币,两个人轮流向前移动一个硬币但是不能超过前一个硬币,无法移动者输。
求有多少种情况先手必胜。


解题思路

竟然有我会的题,我感动

位置做差分再减去\(1\)之后就是一个经典的阶梯博弈问题了,结论就是奇数位置的异或和。

但是这题是计数,先让\(n\)减去\(m\),然后正难则反考虑求总方案和后手必胜的情况,这样问题就变为有多少个长度为\(m\)的非负整数序列满足它们的和不超过\(n\)且奇数位置的异或和为\(0\)

考虑枚举奇数位置的和,奇数位置个数为\(z=\lfloor\frac{m+1}{2}\rfloor\),设\(f_i\)表示\(z\)个数的和为\(i\)时异或和为\(0\)的方案数,这个状态直接计算起来很难搞。

可以枚举每一个位的\(1\)的数量,显然每一个位的\(1\)数量肯是偶数。然后用组合数转移即可。

然后设\(g_i\)表示\(m-z\)个数和不超过\(i\)的方案数,那么有\(g_i=\sum_{j=0}^i\binom{j+m-z-1}{m-z-1}\),前缀和转移就好了。

然后答案就是\(\binom{n+m}{m}-\sum_{i=0}^nf_ig_{n-i}\)(注意这里的\(n\)已经减去\(m\)了),因为模数不是质数直接杨辉三角求就好了。

时间复杂度\(O(nm\log 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\n",(c[n+m][m]-ans+P)%P);
    return 0;
}
posted @ 2021-01-19 19:37  QuantAsk  阅读(75)  评论(0编辑  收藏  举报