bzoj 4589 Hard Nim

1200 石子游戏 V2
题目来源: TopCoder
基准时间限制:4 秒 空间限制:131072 KB 分值: 640 难度:8级算法题 收藏 关注
Nim游戏的规则如下:A B两个人轮流拿石子,A先拿。每次只能从一堆中取若干个,可将一堆全取走,但不可不取,拿到最后1颗石子的人获胜。不同的初始局面,决定了最终的获胜者,有些局面下先拿的会赢,其余的先手会负。
共有N堆石子,每堆石子的数量都是 <= M的质数。求符合条件的先手负的局面有多少种。例如: N = 3(共3堆石子),M = 7,小于M的质数为:2, 3, 5, 7。如果初始局面为(2,5,7)则先手负。同理,(5,7,2)以及其它(2,5,7)的排列,都符合先手负的条件,并且除此之外的其他组合都不能做到先手负。所以符合条件的局面共有3! = 6种。由于符合条件的局面很多,输出Mod 10^9 + 7的结果即可。
注:2 2 3 3同 2 3 2 3 算2种不同的局面。

Input
输入共2个数:N, M中间用空格分隔。N为石子的堆数,M为每堆石子数量的上限,并且每堆石子的数量为质数。(2 <= N <= 10^9,2 <= M <= 50000)。
Output
输出符合条件的局面的数量Mod 10^9 + 7。
Input示例
10 100
Output示例
294844622


【分析】
朴素的想法是做n次DP

然后做n次就炸了。

用到快速我二婶变换,针对异或版本的。

算是个裸题吧…


【代码】

//bzoj 4589 Hard Nim
#include<bits/stdc++.h>
#define N 50000
#define ll long long
#define fo(i,j,k) for(i=j;i<=k;i++)
using namespace std;
const int mxn=50005;
const int mod=1e9+7;
bool vis[mxn];
int n,m,T,inv,L;
int pri[mxn],a[mxn<<1];
inline int ksm(int x,int k)
{
    if(k==1) return x;
    int tmp=ksm(x,k>>1);
    if(k&1) return (ll)tmp*tmp%mod*x%mod;
    else return (ll)tmp*tmp%mod;
}
inline void init()
{
    int i,j;
    inv=ksm(2,mod-2);
    fo(i,2,N)
    {
        if(!vis[i]) pri[++pri[0]]=i;
        for(j=1;j<=pri[0] && (ll)i*pri[j]<=N;j++)
        {
            vis[i*pri[j]]=1;
            if(i%pri[j]==0) break;
        }
    }
}
inline void DWT(int l,int r)  //fan
{
    if(l==r) return;
    int mid=l+r>>1;
    for(int i=l,j=mid+1;i<=mid;i++,j++)
    {
        int x=(ll)(a[j]-a[i]+mod)*inv%mod;
        a[i]=(ll)(a[i]+a[j])*inv%mod;
        a[j]=x;
    }
    DWT(l,mid),DWT(mid+1,r);
}
inline void FWT(int l,int r)  //zheng
{
    if(l==r) return;
    int mid=l+r>>1;
    FWT(l,mid),FWT(mid+1,r);
    for(int i=l,j=mid+1;i<=mid;i++,j++)
    {
        int x=(a[i]+a[j])%mod;
        a[i]=(a[i]-a[j]+mod)%mod;
        a[j]=x;
    }
}
int main()
{
    init();
    int i,j;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        memset(a,0,sizeof a);
        fo(i,1,pri[0]) if(pri[i]<=m) a[pri[i]]=1;
        L=1;while(L<=m) L<<=1;L--;
        FWT(0,L);
        fo(i,0,L) a[i]=ksm(a[i],n);
        DWT(0,L);
        printf("%d\n",a[0]);
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值