小C在学FWT是无意间翻到的一道裸题。
Description
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)的一点学习和思考》要借着这道题说出来的,由于篇幅过长,只好另起一篇了。