BZOJ-4589 Hard Nim
Description
Claris和NanoApe在玩石子游戏,他们有n堆石子,规则如下:
-
Claris和NanoApe两个人轮流拿石子,Claris先拿。
-
每次只能从一堆中取若干个,可将一堆全取走,但不可不取,拿到最后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
题解
首先需要一点博弈论的知识
对于一堆个数为\(x\)的石子,其状态即为\(SG(x)=x\),由SG定理可知,n堆即为全部异或起来,也就是全部异或后若和为0,则先手必败,否则先手必胜
那么这个题就转化为从小于m的质数中挑n个,异或和为0的有几种
于是就可以fwt做了
先fwt求得点值表示,由于没有精度损失,可以直接快速幂求n次方,再求逆即可
fwt用于计算位运算的卷积,如
\[C_k=\sum_{i|j=k}A_i∗B_j \\C_k=\sum_{i\&j=k}A_i∗B_j \\C_k=\sum_{i∧j=k}A_i∗B_j
\]
原理这个东西...搞不太懂,记模板好了,和fft挺像的
下面代码中fwt的与、或和异或都给出了,其中异或是取模意义下的
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
ll qpow(ll a, ll b) {
ll ans = 1;
while (b) {
if (b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
const ll inv2 = qpow(2, mod - 2);
void fwt_and(ll a[], int len, int op) {
for (int h = 2; h <= len; h <<= 1) {
for (int j = 0; j < len; j += h) {
for (int k = j; k < j + h / 2; k++) {
a[k + h / 2] += a[k] * op;
}
}
}
}
void fwt_or(ll a[], int len, int op) {
for (int h = 2; h <= len; h <<= 1) {
for (int j = 0; j < len; j += h) {
for (int k = j; k < j + h / 2; k++) {
a[k] += a[k + h / 2] * op;
}
}
}
}
void fwt_xor(ll a[], int len, int op) {
for (int h = 2; h <= len; h <<= 1) {
for (int j = 0; j < len; j += h) {
for (int k = j; k < j + h / 2; k++) {
ll u = a[k], t = a[k + h / 2];
a[k] = (u + t) % mod;
a[k + h / 2] = (u - t + mod) % mod;
if (op == -1) {
a[k] = a[k] * inv2 % mod;//若不用取模,直接将inv2改成/2即可
a[k + h / 2] = a[k + h / 2] * inv2 % mod;
}
}
}
}
}
const int N = 2e5 + 50;
int notprime[N], prime[N];
ll a[N];
int main() {
int cnt = 0;
for (int i = 2; i < N; i++) {
if (!notprime[i]) prime[++cnt] = i;
for (int j = 1; j <= cnt && i * prime[j] < N; j++) {
notprime[i * prime[j]] = 1;
if (i % prime[j] == 0) break;
}
}
int n, m;
while (~scanf("%d%d", &n, &m)) {
int len = 1;
while (len <= m) len <<= 1;
for (int i = 0; i < len; i++) a[i] = 0;
for (int i = 1; i <= cnt && prime[i] <= m; i++) {
a[prime[i]] = 1;
}
fwt_xor(a, len, 1);
for (int i = 0; i < len; i++) {
a[i] = qpow(a[i], n);
}
fwt_xor(a, len, -1);
printf("%lld\n", a[0]);
}
return 0;
}