@description@
n 堆石子,每堆石子的数量是不超过 m 的一个质数。
两个人玩 nim 游戏,问使后手必胜的初始局面有多少种。
模 10^9 + 7。
input
多组数据。数据组数 <= 80。
每组数据一行两个正整数,n 和 m。1 <= n <= 10^9, 2 <= m <= 50000。
output
对于每组数据,输出一行答案。
sample input
3 7
4 13
sample output
6
120
@solution@
根据常识,异或起来等于 0,nim 游戏就一定后手必胜。
定义 dp(i, j) 前 i 堆石子异或起来等于 j 的方案数,则有转移:
\[dp(i, j) = \sum_{k\in S}dp(i-1, j\oplus k)\]
其中 S 是小于等于 m 的质数集合,初始情况 dp(0, 0) = 1。
这感觉起来像是一个卷积,但是又有点不像我们平常所见的卷积。
定义 \(f(i) = [i\in S]\),则 \(dp(i,j)=\sum f(k)*dp(i-1, j\oplus k)\)。
好的它就是一个异或卷积。
我们的 dp(n)其实就是 f^n。因此,我们只需要先对 f 进行 FWT 正变换,再对每一个数进行快速幂,最后再 FWT 逆变换回来,f(0) 就是我们的答案。
@accepted code@
#include<cstdio>
const int MAXN = 100000;
const int MOD = int(1E9) + 7;
const int INV = (MOD + 1) >> 1;
int pow_mod(int b, int p) {
int ret = 1;
while( p ) {
if( p & 1 ) ret = 1LL*ret*b%MOD;
b = 1LL*b*b%MOD;
p >>= 1;
}
return ret;
}
int f[MAXN + 5], prm[MAXN + 5], pcnt = 0;
bool isprm[MAXN + 5];
void init() {
for(int i=2;i<=MAXN;i++) {
if( !isprm[i] ) {
prm[++pcnt] = i;
f[i] = 1;
}
for(int j=1;1LL*i*prm[j]<=MAXN;j++) {
isprm[i*prm[j]] = true;
if( i % prm[j] == 0 )
break;
}
}
}
void fwt(int *a, int n, int type) {
for(int s=2;s<=n;s<<=1) {
for(int i=0,t=(s>>1);i<n;i+=s) {
for(int j=0;j<t;j++) {
int x = a[i+j], y = a[i+j+t];
a[i+j] = 1LL*(x + y)%MOD*(type == 1 ? 1 : INV)%MOD;
a[i+j+t] = 1LL*(x + MOD - y)%MOD*(type == 1 ? 1 : INV)%MOD;
}
}
}
}
int g[MAXN + 5];
int main() {
init(); int n, m;
while( scanf("%d%d", &n, &m) == 2 ) {
int len = 1; while( len <= m ) len <<= 1;
for(int i=0;i<len;i++)
g[i] = 0;
for(int i=1;i<=m;i++)
g[i] = f[i];
fwt(g, len, 1);
for(int i=0;i<len;i++)
g[i] = pow_mod(g[i], n);
fwt(g, len, -1);
printf("%d\n", g[0]);
}
}
@details@
我才不会说我因为初始化只把m以内的数清零然后WA好几遍呢。