zoukankan      html  css  js  c++  java
  • 【科技】快速莫比乌斯变换(反演) 与 子集卷积

    我们比较了解的是有关多项式的乘法运算,对于下标为整数,下标运算为相加等于某个数的时候,我们有很优秀的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
  • 相关阅读:
    .net log4dll的使用
    Myslq 5.7安装
    接口和抽象类有什么区别
    monkey测试
    JDK、Jmeter、Android环境变量配置
    聊天室
    tushrea知识笔记
    爬取图片
    python gui之tkinter事件处理
    ttk.Treeview
  • 原文地址:https://www.cnblogs.com/Dance-Of-Faith/p/8818211.html
Copyright © 2011-2022 走看看