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

正题

题目链接: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;
}
原文地址:https://www.cnblogs.com/QuantAsk/p/14299621.html