bzoj4589: Hard Nim FWT

本文探讨了bzoj4589:HardNim问题,即两人轮流从多堆不超过特定值的质数石子中取石子的游戏。通过使用FWT(快速沃尔什-哈达玛变换)算法来计算所有可能的异或和为0的组合数量,并提供了详细的算法解释和实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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=ij=kAiBj 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,A0A12) 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(A0A1,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,A1A0) 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值