【科技】快速莫比乌斯变换(反演) 与 子集卷积

我们比较了解的是有关多项式的乘法运算,对于下标为整数,下标运算为相加等于某个数的时候,我们有很优秀的FFT做法。

但是遇到一些奇怪的卷积形式时,比如我们定义 $h = f * g$, $h_{S} = \sum\limits_{L \subseteq S}^{} \sum\limits_{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}} = \sum\limits_{T \subseteq S}^{} f_{T}$。
  • 相反的,我们定义 $\hat{f}$ 的莫比乌斯反演为 $f$,其中 $f_{S} = \sum\limits_{T \subseteq S}^{} (-1)^{|S| - |T|} \hat{f_{T}}$,用容斥原理易得。
  • 然后我们对卷积式两边同时做莫比乌斯变换:$\hat{h_{S}} = \sum\limits_{L \subseteq S} \sum\limits_{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}} = \sum\limits_{L \subseteq S} \sum\limits_{R \subseteq S} f_{L} * g_{R}$。
  • 即 $\hat{h_{S}} = ( \sum\limits_{L \subseteq S} f_{L} ) * ( \sum\limits_{R \subseteq S} g_{R} ) = \hat{f_{S}} * \hat{g_{S}}$。

于是问题就在于如何快速求出 $f$ 和 $g$ 莫比乌斯变换(反演)。

如果要暴力的话,可以直接枚举子集算出莫比乌斯变换(反演),这样是 $O(3^{n})$,虽然比较优秀了,但复杂度还能更低。

我们考虑用递推解决:

  • 设 $\hat{f_{S}}^{(i)}$ 表示 $\sum\limits_{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} = \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

 

posted @ 2018-04-13 10:07 Dance_Of_Faith 阅读(...) 评论(...) 编辑 收藏