[BZOJ]4589 Hard Nim

  小C在学FWT是无意间翻到的一道裸题。

Description

  Claris和NanoApe在玩石子游戏,他们有n堆石子,规则如下:
  1. Claris和NanoApe两个人轮流拿石子,Claris先拿。
  2. 每次只能从一堆中取若干个,可将一堆全取走,但不可不取,拿到最后1颗石子的人获胜。
  不同的初始局面,决定了最终的获胜者,有些局面下先拿的Claris会赢,其余的局面Claris会负。
  Claris很好奇,如果这n堆石子满足每堆石子的初始数量是不超过m的质数,而且他们都会按照最优策略玩游戏,那么NanoApe能获胜的局面有多少种。
  由于答案可能很大,你只需要给出答案对10^9+7取模的值。

Input

  输入文件包含多组数据,以EOF为结尾。
  对于每组数据:共一行两个正整数n和m。

Output

  对于每组数据,输出一个整数作为答案。

Sample Input

  3 7
  4 13

Sample Output

  6
  120

HINT

  每组数据有1<=n<=10^9,2<=m<=50000。不超过80组数据。

 

Solution

  首先是Nim游戏,根据Nim游戏的结论,若所有石子的数量异或起来等于0,那么后手必胜,否则先手必胜。

  所以我们要求的是n个数有多少种取值方案会使得这n个数的异或和为0。

 

  我们先考虑n=2的时候怎么做。

  虽然我们知道,答案等于m以内的质数个数,但是我们还是要想一想最朴素的做法。

  枚举前一个数,枚举后一个数,看看他们的异或和是否等于0。

  这样是O(m^2)的,而且我们能够得到的信息不只是异或和为0的方案数,甚至我们连异或和为x(x>0)的方案数都知道了。

  然后仔细想想这不就是在做FWT吗?

  (快速沃尔什变换FWT:http://www.cnblogs.com/ACMLCZH/p/8022502.html

 

  然后n>2呢?那大概就是n个这样的数组卷积在一起吧。

  由于卷积具有结合律,所以搞个卷积快速幂就可以了。

  时间复杂度O(T*logn*mlogm)。

#include <cstdio>
#include <cstring>
#include <algorithm>
#define MM 135005
#define mod 1000000007
using namespace std;
int ntw,n,m,prin,len;
int pri[MM],a[MM],b[MM];
bool u[MM];

inline int read()
{
    int n=0,f=1; char c=getchar();
    while (c<'0' || c>'9') {if(c=='-')f=-1; c=getchar();}
    while (c>='0' && c<='9') {n=n*10+c-'0'; c=getchar();}
    return n*f;
}

void FWT(int *a,int len,int g)
{
    register int wt,st,i,x,y;
    for (wt=1;wt<len;wt<<=1)
        for (st=0;st<len;st+=wt<<1)
            for (i=0;i<wt;++i)
            {
                x=a[st+i]; y=a[st+wt+i];
                a[st+i]=1LL*g*(x+y)%mod;
                a[st+wt+i]=1LL*g*(x-y+mod)%mod;
            }
}

inline void pro(int* a,int* b,int len) {for (register int i=0;i<len;++i) a[i]=1LL*a[i]*b[i]%mod;}
void mi(int* b,int* a,int z,int len)
{
    FWT(b,len,1); FWT(a,len,1);
    for (;z;z>>=1,pro(a,a,len)) if (z&1) pro(b,a,len);
    FWT(b,len,ntw);
}

int main()
{
    register int i,j;
    for (i=2;i<MM;++i)
    {
        if (!u[i]) pri[++prin]=i;
        for (j=1;i*pri[j]<MM;++j)
        {
            u[i*pri[j]]=true;
            if (i%pri[j]==0) break;
        }
    }
    ntw=(mod+1)/2;
    while (scanf("%d%d",&n,&m)!=EOF)
    {
        memset(a,0,sizeof(a));
        memset(b,0,sizeof(b));
        for (i=1;pri[i]<=m;++i) ++a[pri[i]];
        for (len=1;len<=pri[i-1];len<<=1);
        b[0]=1;    mi(b,a,n,len);
        printf("%d\n",b[0]);
    }
}

 

Last Word

  本来《关于快速沃尔什变换(FWT)的一点学习和思考》要借着这道题说出来的,由于篇幅过长,只好另起一篇了。

posted @ 2017-12-11 19:52  ACMLCZH  阅读(375)  评论(0编辑  收藏  举报