快速沃尔什变换(FWT)快速莫比乌斯变换(FMT)
jiazhaopeng · · 个人记录
- FWT快速沃尔什变换学习笔记
虽然一点不懂,但是看着代码短得感人,背过就好了吧。
或
与
异或
代码实现
记忆
有些东西不用全靠硬背,可以有技巧地背
或
进行或运算,通常数会往大里走,所以较高位的数要加上较低位的数 (雾)
与
与 与 或 相反,通常数会往小里走,于是 是较低位的数加上较高位的数 (大雾)
异或
有点特殊? 有点像 FFT?那就记成 FFT 的形式就好了吧...
IFWT
IFWT 就是把 FWT 给还原一下就好了吧。
之前某位置加上了一个数,而现在那个数还没变 (大雾),那么就把那个数减掉就好了吧。
(对于异或)找不到之前那个数?没关系,可以列个方程解出来 (大雾)
注意!!
-
FWT不用倍长,不用蝴蝶变换
-
FWT里面还是尽量用<=比较安全,只要空间足够就没啥问题。特别注意一点:选取limit时要while (limi <= n)!!这时候一定要小于等于,否则不全!!
-
其余各项同ntt里面的注意
补充说明:
对于或卷积:(
发现
子集卷积
P6097 【模板】子集卷积
给定
即在
如果不要求互不相交,那么就是个 FWT_OR 的板子题了。如果要求互不相交,那么只需加上限制
于是,我们可以枚举
例题:P6570 [NOI Online #3 提高组]优秀子序列(民间数据)(洛谷数据可以被暴力水过,随机数据下本机也可过,但是CCF的评测机太慢,过不去)
例题:P4221 [WC2018]州区划分:自己卷自己的子集卷积。
7.29 Update:
从ztb那里学到的一些东西(对快速沃尔什变换的深入理解,后来发现似乎是快速莫比乌斯变换)
或卷积
我们定义一个高维前缀和数组:
我们发现如果这样的卷积符合 或卷积 的性质:
这样就可以
如何求 (for(维度i) for (所有状态S) S += S - i),其中
与卷积
我们定义一个类高维前缀和数组:
这个数组符合与卷积的性质:
代码实现就是反过来(0看作1,1看作0)做一遍高维前缀和。
异或卷积
这个就用不了 FMT 了。
我们定义一个根本不是前缀和的辅助数组:
然后艰难地寻找其有关卷积的性质:
看起来推不下去了?仔细观察
考虑按位拆分统计贡献(这显然是可行的),先刨除掉
于是我们有了一个惊人的发现:
这样我们就可以快速推异或卷积了。
可是这个 分奇偶分组分治讨论。
一维一维地考虑。初始时(只考虑自己)
然后是逆变换。实际上还有个式子:
也比较好证,展开一下
右上角的式子很熟悉,它等于:
关键在于证得:
这是因为只要
然后就什么都好说了,这里就不再证了。
而这个
(其实最简单的方法莫过于干了什么就“撤回”什么,可以直接推得沃尔什逆变换)。
小结
- 或:
意义:(同高维前缀和)
- 与:
意义:包含
- 异或:
意义:?????
2021.3.31 Update
对异或卷积有了新的理解。
可能需要先了解一下集合幂级数的概念。可以在吕凯风的集训队论文中找到。
观察异或运算,其本质上是二进制不进位加法,那么下标上的异或运算就是做长度为 2 的循环卷积,可以使用 FFT。并且,
考虑
下面目前还不太理解。对于高维的情况,其 FFT 就是对每一维进行一遍 FFT。然后就得到了我们的 FWT 算法。
于是,我们不仅可以做 二维的不进位加法卷积,也可以做更高维的不进位加法卷积(如果模数友好存在那些单位根)
应用
4589: Hard Nim
经典Nim游戏(轮流取石子,无法操作者为负)。
n堆石子满足每堆石子的初始数量是不超过m的质数。
求先手必败局面数。
n <= 1e9, m <= 5e4
最朴素的方法自然是枚举每一种方案,判断其合法不合法。
复杂度:O(
当只有两个sigma(n=2)的时候是:
即
是异或卷积的形式。
(思维逐渐混乱)
根据FFT相关计数题目的经验,我们可以用权值数组。即
考虑类似快速幂的方法,以快速幂的格式,把
一想到多项式快速幂,我们就成功的走向了大弯路,甚至复杂度都有点保不住。不要忘记FFT,NTT,FWT都是借助点值表示加速的本质。在转化成点值表示以后,仍支持交换律,结合律等,因此可以直接把每个点值做快速幂。复杂度
limi = 1;
while (limi <= m) limi <<= 1;
for (register int i = 0; i <= m; ++i) A[i] = (!depri[i]);
FWT_xor(A, 1);
for (register int i = 0; i <= limi; ++i) A[i] = quickpow(A[i], n);
FWT_xor(A, -1);
printf("%lld\n", A[0]);
CF662C Binary Table
先看题解 CF662C 【Binary Table】吧。
有一个 n 行 m 列的表格,每个元素都是 0/1 ,每次操作可以选择一行或一列,把 0/1 翻转,即把 0 换为 1 ,把 1 换为 0 。请问经过若干次操作后,表格中最少有多少个 1 。
考虑枚举行翻转情况为State。设一开始第 i 列的情况为
转换枚举对象: 枚举 对行操作后的列状态 X(方便直接使用
稍作变换:
即:
然后预处理出 F 和 Q ,FWT即可。
练习题
P3175 [HAOI2015]按位或
A国的贸易
附
FWT模板(调试用)
inline void FWT_or(ll *a, int type) {
for (register int i = 1; i < limi; i <<= 1) {
for (register int j = 0; j < limi; j += (i << 1)) {
for (register int p = 0; p < i; ++p) {
a[i + j + p] = (a[i + j + p] + a[j + p] * type) % P;
if (a[i + j + p] < 0) a[i + j + p] += P;
}
}
}
}
inline void sol_or() {
memcpy(tp1, A, sizeof(A));
memcpy(tp2, B, sizeof(B));
FWT_or(tp1, 1); FWT_or(tp2, 1);
for (register int i = 0; i < limi; ++i) C[i] = tp1[i] * tp2[i] % P;
FWT_or(C, -1);
for (register int i = 0; i < limi; ++i) printf("%lld ", C[i]);
puts("");
}
inline void FWT_and(ll *a, int type) {
for (register int i = 1; i < limi; i <<= 1) {
for (register int j = 0; j < limi; j += (i << 1)) {
for (register int p = 0; p < i; ++p) {
a[j + p] = (a[j + p] + a[i + j + p] * type) % P;
if (a[j + p] < 0) a[j + p] += P;
}
}
}
}
inline void sol_and() {
memcpy(tp1, A, sizeof(A));
memcpy(tp2, B, sizeof(B));
FWT_and(tp1, 1); FWT_and(tp2, 1);
for (register int i = 0; i < limi; ++i) C[i] = tp1[i] * tp2[i] % P;
FWT_and(C, -1);
for (register int i = 0; i < limi; ++i) printf("%lld ", C[i]);
puts("");
}
inline void FWT_xor(ll *a, int type) {
for (register int i = 1; i < limi; i <<= 1) {
for (register int j = 0; j < limi; j += (i << 1)) {
for (register int p = 0; p < i; ++p) {
ll nx = a[j + p], ny = a[i + j + p];
a[j + p] = (nx + ny) % P;
a[i + j + p] = (nx - ny + P) % P;
if (type == -1) {
a[j + p] = a[j + p] * inv2 % P;
a[i + j + p] = a[i + j + p] * inv2 % P;
}
}
}
}
}
inline void sol_xor() {
memcpy(tp1, A, sizeof(A));
memcpy(tp2, B, sizeof(B));
FWT_xor(tp1, 1); FWT_xor(tp2, 1);
for (register int i = 0; i < limi; ++i) C[i] = tp1[i] * tp2[i] % P;
FWT_xor(C, -1);
for (register int i = 0; i < limi; ++i) printf("%lld ", C[i]);
puts("");
}