咱们比较了解的是有关多项式的乘法运算,对于下标为整数,下标运算为相加等于某个数的时候,咱们有很优秀的FFT作法。算法
可是遇到一些奇怪的卷积形式时,好比咱们定义 $h = f * g$, $h_{S} = \sum\limits_{L \subseteq S}^{} \sum\limits_{R \subseteq S}^{} [L \cup R = S] f_{L} * g_{R}$。spa
此时下标是一个集合,运算为集合并的卷积,咱们已知了 $f$ 和 $g$ ,须要快速算出 $h$。code
最暴力的作法是 $O(2^{n})$ 分别枚举 $L$ 和 $R$,把答案加到 $h$ 中去,这样复杂度是 $O(4^{n})$,不太行。blog
这时咱们就要一种高效的算法。求卷积能够用分治乘法,好像比较高妙,但咱们要讲的是另外一种:快速莫比乌斯变换和反演。it
类比FFT,FMT也须要先把 $f$ 和 $g$ 求点值,点值相乘后再插值回去,快速莫比乌斯变换就至关于点值,快速莫比乌斯反演就至关于插值。class
具体证实:原理
因而问题就在于如何快速求出 $f$ 和 $g$ 莫比乌斯变换(反演)。循环
若是要暴力的话,能够直接枚举子集算出莫比乌斯变换(反演),这样是 $O(3^{n})$,虽然比较优秀了,但复杂度还能更低。方法
咱们考虑用递推解决:im
这样咱们就能在 $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} = \sum\limits_{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} = \sum\limits_{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