写在前面
本蒟蒻很菜,所以这篇博客依然几乎不会有证明
还是先说这东西是干什么的
平时我们所见的卷积是这样的:
[h(k) = sum_{i + j = k} f(i) cdot g(j)
]
但是偶尔还会遇到条件中的加号变成其它符号的情况,(FWT)就是这个符号是按位与,或,异或时快速求解(h)的
主要的三种情况
其实就是我们要构造一种变换(tf),使得(tf(h) = tf(f) cdot tf(g)),其中点乘表示对应位相乘
或卷积(|)
即:
[h(k) = sum_{i | j = k} f(i) cdot g(j)
]
我们可以构造变换(FWT(A)),使得(FWT(A)_i = sum_{j | i = i} A_j)(我也不知道这个方法是怎么想出来的……)
但是验证它还是不难的,首先有这个:(i | j | k = k Rightarrow i | k = k, j | k = k)
那么
[egin{align}
FWT(h)_i = sum_{j | i = i} h_j & = sum_{j | i = i} sum_{s | t = j} f(s) cdot g(t) \
& = sum_{s | i = i} sum_{t | i = i} f(s) cdot g(t) \
& = sum_{s | i = i} f(s) sum_{t | i = i} g(t) = FWT(f)_i cdot FWT(g)_i
end{align}
]
然后问题变成了快速求(FWT(A))
由于这个变换满足下面的等式:
[FWT(A) = (FWT(A_0), FWT(A_0) + FWT(A_1))
]
其中(A_0)表示序列前半段,(A1)表示序列后半段,加号表示对应位置相加。简单理解一下就是根据定义,(FWT(A)_i)是(i)的所有子集(j)处(A_j)之和,而序列前半段的下标二进制最高位为(0),增加这一位并不会产生新的子集,后半段下标二进制最高位为(1),增加这一位后前面高位为(0)的部分也会成为新的子集
所以显然就可以每次将序列分成前后两段分别计算,然后合并即可
然后怎么逆变换呢,根据上面的正变换,不难发现就是减掉所有它的子集处的值就是了,可以得出:
[IFWT(A) = (IFWT(A_0), IFWT(A_1) - IFWT(A_0))
]
与卷积(&)
与和或在各个方面都联系紧密,那么就可以类比或卷积,不难得出:
[egin{align}
FWT(A) & = (FWT(A_0) + FWT(A_1), FWT(A_1)) \
IFWT(A) & = (IFWT(A_0) - IFWT(A_1), IFWT(A_1))
end{align}
]
异或卷积(^)
这个比较复杂,蒟蒻我并没能吃透,好像和奇偶性有关??
结论是:
[egin{align}
FWT(A) & = (FWT(A_0) + FWT(A_1), FWT(A_0) - FWT(A_1)) \
IFWT(A) & = (frac{IFWT(A_0) + IFWT(A_1)}{2}, frac{IFWT(A_0) - IFWT(A_1)}{2})
end{align}
]
另外一个很少见的运算——同或((odot))
直接上结论了:
[egin{align}
FWT(A) & = (FWT(A_1) - FWT(A_0), FWT(A_1) + FWT(A_0)) \
IFWT(A) & = (frac{IFWT(A_1) - IFWT(A_0)}{2}, frac{IFWT(A_1) + IFWT(A_0)}{2})
end{align}
]
代码
模板题戳这里
因为没见到同或的题,也没有看见过同或的模板,下面的代码只有前三种运算QwQ
还有就是取模意义下除以(2)转换为乘上逆元QwQ
void inc(LL &x, LL y) { x += y; if (x >= mod) x -= mod; }
void dec(LL &x, LL y) { x -= y; if (x < 0) x += mod; }
/*
type == 0 => or
type == 1 => and
type == 2 => xor
*/
void FWT(LL *arr, int n, int type) {
for (int len = 2, half = 1; len <= (1 << n); len <<= 1, half <<= 1)
for (int i = 0; i < (1 << n); i += len)
for (int j = 0; j < half; ++j)
if (type == 0) inc(arr[i + half + j], arr[i + j]);
else if (type == 1) inc(arr[i + j], arr[i + half + j]);
else {
LL a0 = arr[i + j], a1 = arr[i + half + j];
inc(arr[i + j], a1); dec(arr[i + half + j] = a0, a1);
}
}
void IFWT(LL *arr, int n, int type) {
for (int len = 2, half = 1; len <= (1 << n); len <<= 1, half <<= 1)
for (int i = 0; i < (1 << n); i += len)
for (int j = 0; j < half; ++j)
if (type == 0) dec(arr[i + half + j], arr[i + j]);
else if (type == 1) dec(arr[i + j], arr[i + half + j]);
else {
LL a0 = arr[i + j], a1 = arr[i + half + j];
arr[i + j] = (a0 + a1) * inv % mod;
arr[i + half + j] = (a0 - a1) * inv % mod;
if (arr[i + half + j] < 0) arr[i + half + j] += mod;
}
}