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
Sample Output
6
120
HINT
Source
Topcoder SRM 518 Div1 Hard Nim By Tangjz
神题一道…..
一开始写了个
O(n3)
,然后优化成
O(n2log2n)
,最后去学了FWT……
这很好~
思路:
首先你需要知道,答案是在求所有异或和为0的情况数。
不知道的请出门左转博弈论。
然后,很显然有一个
O(n3)
的DP:
令
f[i][j]
表示已经有
i
堆石子,异或和为
那么
f[i][j∧prime[k]]+=f[i−1][j]
复杂度
O(nm2)
,咱们得到了一个很优秀的算法。
考虑优化:
令
f[i][j]
表示已经有
2i
堆石子,异或和为
j
时的方案数。
那么
复杂度
O(m2log2n)
,咱们又得到了一个十分优秀的算法。
考虑使用FWT优化转移。
FWT的作用是,进行形如下方的变换:
ci=∑j⊕k==iai
其中
⊕
表示某种逻辑运算符,如OR、XOR、AND……
这里可以发现,上方的式子,可以被如下表示:
f[i][j∧k]=∑j∧k==if[i−1][j]∗f[i−1][k]
这正是一个卷积的形式,而且正是FWT想要的形式。
使用FWT,只需要像用FFT一样求出点值表达式再逆运算回去即可。
对于每一种逻辑运算符,FWT的写法都不同。这里需要的是XOR版。
其实和FFT是相同的,只是不需要乘上复数根,而是直接进行加减法。
求IFWT运算时,只需要在每次进行蝶形操作时把所得结果均除以2即可。
于是就被优化成了 O(mlog2mlog2n) …..
然而可以发现,其实没有必要每次在求出点值表达式后再变回原式,因为反正一会还要再变成点值表达式进行下一次运算……
那咱们就只需要进行一次FWT,m次快速幂,再进行一次IFWT,就完成了。
时间复杂度
O(mlog2n+mlog2m)
~
搞定收工~
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
using namespace std;
typedef long long ll;
const int M=100009;
const ll md=1e9+7;
const ll inv=(md+1)>>1;
ll n,m,p;
ll pri[M],ptop;
bool npri[M];
ll ans[M];
inline void chk(ll &a){if(a>=md)a-=md;}
inline void mchk(ll &a){if(a<0)a+=md;}
inline ll qpow(ll a,ll b)
{
ll ret=1;
while(b)
{
if(b&1ll)
ret=ret*a%md;
a=a*a%md;
b>>=1;
}
return ret;
}
inline void init()
{
for(int i=2;i<M;i++)
{
if(!npri[i])
pri[++ptop]=i;
for(int j=1;j<=ptop && i*pri[j]<M;j++)
{
npri[i*pri[j]]=1;
if(i%pri[j]==0)
break;
}
}
}
inline void FWT(ll *a,int n,bool f)
{
for(int i=2;i<=n;i<<=1)
for(int j=0;j<n;j+=i)
for(int k=j;k<j+(i>>1);k++)
{
ll x=a[k],y=a[k+(i>>1)];
chk(a[k]=x+y);
mchk(a[k+(i>>1)]=x-y);
if(f)
{
(a[k]*=inv)%=md;
(a[k+(i>>1)]*=inv)%=md;
}
}
}
int main()
{
init();
while(scanf("%lld%lld",&n,&m)!=EOF)
{
memset(ans,0,sizeof(ans));
for(int i=1;i<=ptop && pri[i]<=m;i++)
ans[pri[i]]=1ll;
for(p=1;p<=m;p<<=1);
FWT(ans,p,0);
for(int j=0;j<p;j++)
ans[j]=qpow(ans[j],n);
FWT(ans,p,1);
printf("%lld\n",ans[0]);
}
return 0;
}