【BZOJ4589】Hard Nim
Description
Claris和NanoApe在玩石子游戏,他们有n堆石子,规则如下:
1. Claris和NanoApe两个人轮流拿石子,Claris先拿。
2. 每次只能从一堆中取若干个,可将一堆全取走,但不可不取,拿到最后1颗石子的人获胜。
不同的初始局面,决定了最终的获胜者,有些局面下先拿的Claris会赢,其余的局面Claris会负。
Claris很好奇,如果这n堆石子满足每堆石子的初始数量是不超过m的质数,而且他们都会按照最优策略玩游戏,那么NanoApe能获胜的局面有多少种。
由于答案可能很大,你只需要给出答案对10^9+7取模的值。
Input
输入文件包含多组数据,以EOF为结尾。
对于每组数据:
共一行两个正整数n和m。
每组数据有1<=n<=10^9, 2<=m<=50000。
不超过80组数据。
Output
Sample Input
3 7
4 13
4 13
Sample Output
6
120
120
题解:NIM游戏先手必胜的条件是所有堆得石子数异或和为0,原因不说了。
我们来学习fwt吧!
要想深入理解原理还要去看picks的博客,不过吾等蒟蒻只需要理解一点表面含义即可。
fwt与fft类似,也是将一个数组变成一个特殊形式使得我们可以直接将两个特殊形式按位相乘,然后再从这个特殊形式变回来。而具体怎么变呢?对于异或运算,有位大神构造出了这样一个变换:
fwt:将fft的蝴蝶变换部分改为(a[k],a[k+h/2])->(a[k]+a[k+h/2],a[k]-a[k+h/2])。
ufwt:(a[k],a[k+h/2])->((a[k]+a[k+h/2])/2,(a[k]-a[k+h/2])/2)
然后就没了,对于本题,直接快速幂+fwt即可。
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
typedef long long ll;
const ll P=1000000007;
const ll rev=500000004;
int n,m,len,num;
const int maxn=50010;
ll A[(1<<18)+5],B[(1<<18)+5];
int pri[maxn],np[maxn];
void fwt(ll *a)
{
int j,k,h;
ll x,y;
for(h=2;h<=len;h<<=1)
for(j=0;j<len;j+=h)
for(k=j;k<j+h/2;k++)
x=a[k],y=a[k+h/2],a[k]=(x+y)%P,a[k+h/2]=(x-y+P)%P;
}
void ufwt(ll *a)
{
int j,k,h;
ll x,y;
for(h=2;h<=len;h<<=1)
for(j=0;j<len;j+=h)
for(k=j;k<j+h/2;k++)
x=a[k],y=a[k+h/2],a[k]=(x+y)*rev%P,a[k+h/2]=((x-y)*rev%P+P)%P;
}
void pow(ll *a,ll y)
{
ll *b=B;
b[0]=1;
fwt(a),fwt(b);
while(y)
{
if(y&1)
{
for(int i=0;i<len;i++) b[i]=a[i]*b[i]%P;
}
for(int i=0;i<len;i++) a[i]=a[i]*a[i]%P;
y>>=1;
}
ufwt(b);
}
int main()
{
int i,j;
for(i=2;i<=50000;i++)
{
if(!np[i]) pri[++num]=i;
for(j=1;j<=num&&i*pri[j]<=50000;j++)
{
np[i*pri[j]]=1;
if(i%pri[j]==0) break;
}
}
while(scanf("%d%d",&n,&m)!=EOF)
{
for(len=1;len<=m;len<<=1);
memset(A,0,sizeof(A));
memset(B,0,sizeof(B));
for(i=1;i<=num&&pri[i]<=m;i++) A[pri[i]]=1;
pow(A,n);
printf("%lld\n",B[0]);
}
return 0;
}