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组数据。
Sample Input
3 7
4 13
Sample Output
6
120
分析
算法详解:FWT
这是一个很神奇的玩意儿
用来解决:
Ck=∑i⊗j=kAi⋅Bj
C
k
=
∑
i
⊗
j
=
k
A
i
⋅
B
j
也就是异或和卷积。
当然你也可以把异或运算换成或和与运算。
基础的思路和
FFT
F
F
T
差不多,都是分治。
我们希望找到一种变换
tf
t
f
,使得。
tf(Ci)=tf(Ai)⋅tf(Bi)
t
f
(
C
i
)
=
t
f
(
A
i
)
⋅
t
f
(
B
i
)
对于异或
tf(A)=(tf(A0)+tf(A1),tf(A0)−tf(A1))
t
f
(
A
)
=
(
t
f
(
A
0
)
+
t
f
(
A
1
)
,
t
f
(
A
0
)
−
t
f
(
A
1
)
)
utf(A)=utf(A0+A12,A0−A12)
u
t
f
(
A
)
=
u
t
f
(
A
0
+
A
1
2
,
A
0
−
A
1
2
)
对于与
tf(A)=(tf(A0)+tf(A1),tf(A1))
t
f
(
A
)
=
(
t
f
(
A
0
)
+
t
f
(
A
1
)
,
t
f
(
A
1
)
)
utf(A)=utf(A0−A1,A1)
u
t
f
(
A
)
=
u
t
f
(
A
0
−
A
1
,
A
1
)
对于或
tf(A)=(tf(A0),tf(A1)+tf(A1))
t
f
(
A
)
=
(
t
f
(
A
0
)
,
t
f
(
A
1
)
+
t
f
(
A
1
)
)
utf(A)=utf(A0,A1−A0)
u
t
f
(
A
)
=
u
t
f
(
A
0
,
A
1
−
A
0
)
证明可以看这里
闷声背公式就好了,这篇blog只是凑个数,逃)
例题详解
原题要求的就是取n个不大于m的素数异或为0的方案数。
把每个不大于0的素数下标置为1,FWT后卷积n次即可。
代码
#include<cstdio>
const int N = 65536, P = 1e9 + 7, Tw = P + 1 >> 1;
int pr[N], a[N], tp, L; bool v[N];
int Ad(int a, int b) {a += b; return a >= P ? a - P : a;}
int Dc(int a, int b) {a -= b; return a < 0 ? a + P : a;}
void Tm(int &a, int b) {a = 1LL * a * b % P;}
void Pre(int n) {
v[0] = v[1] = true;
for(int i = 2;i <= n; ++i) {
if(!v[i]) pr[++tp] = i;
for(int j = 1;j <= tp && i * pr[j] <= n; ++j) {
v[i * pr[j]] = true;
if(!(i % pr[j])) break;
}
}
}
void Fwt(int *F, int f) {
for(int i = 1; i < L; i <<= 1)
for(int j = 0; j < L; j += i << 1)
for(int l = 0; l < i; ++l) {
int x = F[j + l], y = F[j + l + i];
F[j + l] = Ad(x, y); F[j + l + i] = Dc(x, y);
if(!~f) Tm(F[j + l], Tw), Tm(F[j + l + i], Tw);
}
}
int pw(int x, int k) {int r = 1; for(;k; Tm(x, x), k >>= 1) if(k & 1) Tm(r, x); return r;}
int main() {
Pre(5e4); int n, m;
for(;~scanf("%d%d", &n, &m);) {
for(L = 1; (L <<= 1) <= m;) ;
for(int i = 0; i < L; ++i) a[i] = (!v[i]) & (i <= m);
Fwt(a, 1);
for(int i = 0;i < L; ++i) a[i] = pw(a[i], n);
Fwt(a, -1);
printf("%d\n", a[0]);
}
return 0;
}