我们比较了解的是有关多项式的乘法运算,对于下标为整数,下标运算为相加等于某个数的时候,我们有很优秀的FFT做法。
但是遇到一些奇怪的卷积形式时,比如我们定义 $h = f * g$, $h_{S} = sumlimits_{L subseteq S}^{} sumlimits_{R subseteq S}^{} [L cup R = S] f_{L} * g_{R}$。
此时下标是一个集合,运算为集合并的卷积,我们已知了 $f$ 和 $g$ ,需要快速算出 $h$。
最暴力的做法是 $O(2^{n})$ 分别枚举 $L$ 和 $R$,把答案加到 $h$ 中去,这样复杂度是 $O(4^{n})$,不太行。
这时我们就要一种高效的算法。求卷积可以用分治乘法,好像比较高妙,但我们要讲的是另一种:快速莫比乌斯变换和反演。
类比FFT,FMT也需要先把 $f$ 和 $g$ 求点值,点值相乘后再插值回去,快速莫比乌斯变换就相当于点值,快速莫比乌斯反演就相当于插值。
具体证明:
- 我们定义 $f$ 的莫比乌斯变换为 $hat{f}$ ,其中 $hat{f_{S}} = sumlimits_{T subseteq S}^{} f_{T}$。
- 相反的,我们定义 $hat{f}$ 的莫比乌斯反演为 $f$,其中 $f_{S} = sumlimits_{T subseteq S}^{} (-1)^{|S| - |T|} hat{f_{T}}$,用容斥原理易得。
- 然后我们对卷积式两边同时做莫比乌斯变换:$hat{h_{S}} = sumlimits_{L subseteq S} sumlimits_{R subseteq S} [L cup R subseteq S] f_{L} * g_{R}$。
- 由于 $[L cup R subseteq S] Leftrightarrow [L subseteq S][R subseteq S]$,所以 $hat{h_{S}} = sumlimits_{L subseteq S} sumlimits_{R subseteq S} f_{L} * g_{R}$。
- 即 $hat{h_{S}} = ( sumlimits_{L subseteq S} f_{L} ) * ( sumlimits_{R subseteq S} g_{R} ) = hat{f_{S}} * hat{g_{S}}$。
于是问题就在于如何快速求出 $f$ 和 $g$ 莫比乌斯变换(反演)。
如果要暴力的话,可以直接枚举子集算出莫比乌斯变换(反演),这样是 $O(3^{n})$,虽然比较优秀了,但复杂度还能更低。
我们考虑用递推解决:
- 设 $hat{f_{S}}^{(i)}$ 表示 $sumlimits_{T subseteq S} [(S - T) subseteq {1, 2, ..., i}] f_{T}$
- 易得初始状态:$hat{f_{S}}^{(0)} = f_{S}$
- 对于每一个不包含 ${i}$ 的集合 $S$,可知 $hat{f_{S}}^{(i)} = hat{f_{S}}^{(i - 1)}$(因为 $S$ 并没有 $i$ 这位),$hat{f}_{S cup {i}}^{(i)} = hat{f}_{S}^{(i - 1)} + hat{f}_{S cup {i}}^{(i - 1)}$(前者的 $T$ 没有包含 ${i}$,而后者的 $T$ 必须包含了 ${i}$)。
- 显然,递推了 $n$ 轮之后,$hat{f}_{S}^n$ 就是所求的变换了。
这样我们就能在 $O(n * 2^{n})$ 快速求出 $f$ 的莫比乌斯变换了。(逆莫比乌斯变换同理)
于是我们就解决了集合并卷积的问题。
UPD:
我们都知道第一层循环枚举集合,第二层循环枚举它为$1$的位,把去掉这个$1$的子集的答案加上去的做法是错的。我们考虑两个集合$s, t$,其中$t in s$。$t$可能有多种路径到达$s$,也就是存在多个$k, k in s, t in k$,这样$t$就会被算多次。
这里有一个感性理解的方法,为什么第一层枚举位第二层枚举集合是对的,也就是每一个集合它的所有子集的贡献只被算了一次。
我们假设$k_{1}$为$t$并上第一个和$s$不一样的位,我们发现$t$的答案会先算到$k_{1}$上,而对于其他的$k$,在$t$的答案算上来的时候自己的答案已经会先算上去了。
而对于逆莫比乌斯变换,如果理解了莫比乌斯变换后,其本质就是一个容斥。
void Fmt(int *a) { for (int i = 0; i < n; ++i) for (int s = 0; s < U; ++s) if (s >> i & 1) a[s] = Add(a[s], a[s ^ (1 << i)]); }
void Ifmt(int *a) { for (int i = 0; i < n; ++i) for (int s = 0; s < U; ++s) if (s >> i & 1) a[s] = Sub(a[s], a[s ^ (1 << i)]); }
(此处$n$为集合大小, $U = 2^n$)
接下来我们来继续讲一讲子集卷积:
问题是已知 $f$ 和 $g$,我们想求出 $h = f * g$,其中 $h_{S} = sumlimits_{T subseteq S} f_{T} * g_{S - T}$。
回顾刚刚的集合并卷积,子集卷积的条件比集合并卷积更苛刻,即 $L$ 和 $R$ 的集合应该不相交。
考虑集合并卷积合法当且仅当 $L cap R = varnothing$,我们可以在卷积时多加一维,维护集合的大小,如 $f_{i,S}$ 表示集合中有 $i$ 个元素,集合表示为 $S$。可以发现当 $i$ 和 $S$ 的真实元素个数符合时才是对的。
初始时,我们只把 $f_{bc[S],S}$ 的值赋成原来的 $f_{S}$($g$ 同理),然后对每一个$f_i$做一遍FMT,点值相乘时这么写:$h_{i, S} = sumlimits_{j = 0}^{i} f_{j,S} * g_{i - j, S}$。最后扫一遍把不符合实际情况的状态赋成 $0$即可。($bc[]$表示集合元素个数,即$bitcount$)
for i = 0 to n Fmt(f[i]) Fmt(g[i]) for s = 0 to U - 1 for j = 0 to i h[i][s] += f[j][s] * g[i - j][s] Ifmt(h[i]) for s = 0 to U - 1 h[bc[s]][s] is the real answer