集合幂级数与子集卷积入门

· · 算法·理论

集合幂级数

在 FMT 和 FWT 里有提到过。

对于一个序列 a_0\sim a_N,定义一个多元多项式 A(x_1\sim x_n)=\sum_{0\le I<2^{n}}a_I\cdot x_1^{i_1}x_2^{i_2}\cdots x_n^{i_n},其中 i_1\sim i_nI 这个集合的二进制表示。

这个多项式有 2^n 项,每一项的系数就是 a。这个 A 叫做集合幂级数。可以类比序列对应到 OGF 上,集合就会对应集合幂级数。

相较于 FMT 和 FWT 处,我们进一步定义集合幂级数的乘法。这是一个比较有用的概念。

简记集合幂级数为 A(x)=\sum a_Ix^I,给定 A(x)=\sum_{I}a_Ix^I,B(x)=\sum_{J}a_Jx^J,怎么定义 A(x)\cdot B(x)

关键在于怎么定义单项式的相乘,也就是 x^I\cdot x^J 怎么定义。在普通单项式乘法里,x^I\cdot x^J=x^{I+J}。但是对于集合幂级数,我们有多种定义。

无论对于哪种定义,都满足交换律和结合律。同时,我们发现这三种定义和与、或、异或卷积都有关系。下面我们来看看。

引理:在 x^I\cdot x^J=x^{I\cup J} 时,A(x)\cdot B(x) 所得的系数刚好是 a,b 的或卷积。

证明

A(x)\cdot B(x)=(\sum_{I}a_Ix^I)\cdot(\sum_{J}b_Jx^J)=\sum_{I,J}a_Ib_Jx^{I\cup J}=\sum_{K}(\sum_{I\cup J=K}a_Ib_J)x^K

同理,把或卷积替换为与卷积、异或卷积,证明都是类似的。

这个引理表明在做集合幂级数乘法时,仍然可以使用卷积快速求得结果。类比多项式乘法用 FFT 快速计算。

子集卷积

假设我们有两个长为 2^n 的序列 a,b,定义其子集卷积 c=a*b,满足:

c_I=\sum_{J\cup K=I,J\cap K=\empty}a_Jb_K

就是或卷积额外要求其交集为空。也就是要求 J,KI 的划分。

这个问题可以 O(n^2\cdot 2^n) 求,而且有两种解法。

法一

来个新定义。

\begin{cases} a^{(t)}_J=a_J\cdot [|J|=t]\\ b^{(t)}_K=b_K\cdot [|K|=t]\\ \end{cases}

这可以理解为一个拆分,把序列按下标的集合大小分为不同类。

观察到 c_I=\sum_{t=0}^{|I|}\sum_{J\cup K=I}a^{(t)}_Jb^{(|I|-t)}_K。因为 J\cup K=IJ\cap K=\empty,所以 |J|+|K|=|I|。于是就可以枚举 |J|=t,则有 |K|=|I|-t

然后怎么求 c

a^{(t)}b^{(t')} 的或卷积记作 c^{(t,t')}。那么 c_I=\sum_{t+t'=|I|}c^{(t,t')}_I

容易给出一个算法:

  1. 预处理 c^{(t,t')}

  2. 枚举 |I|=i

  3. 枚举 (t,t') 满足 t+t'=i

  4. c^{(t,t')} 中大小为 i 的项加入 c_I

分析一下复杂度。

第一步预处理,O(n^2\cdot n\cdot 2^n)。之后统计答案,

还是太慢了,我们再来一个优化:

定义 c^{(i)}_I=\sum_{t+t'=i}c^{(t,t')}_I。目标转化为求 c_I^{(0\sim n)}

c^{(i)} 怎么求?

\begin{aligned} c^{(i)}&=\sum_{t+t'=i}c^{(t+t')}\\ &=\mathrm{iDMT}(\mathrm{DMT}(\sum_{t+t'=i}c^{(t,t')}))\\ &=\mathrm{iDMT}(\sum_{t+t'=i}\mathrm{DMT}(c^{(t,t')}))\\ &=\mathrm{iDMT}(\sum_{t+t'=i}\mathrm{DMT}(c^{(t,t')}))\\ &=\mathrm{iDMT}(\sum_{t+t'=i}\mathrm{DMT}(a^{(t)})\cdot \mathrm{DMT}(b^{(t')})) \end{aligned}

:这里的点乘就是向量点乘。

注 2:这里能把 \mathrm{DMT} 拿进括号里,是因为它是线性变换。

先预处理 a^{(x)},b^{(x)}\mathrm{DMT} 序列,是 O(2n\cdot n\cdot 2^n)=O(n^22^n) 的。

然后 (\sum_{t+t'=i}\mathrm{DMT}(a^{(t)})\cdot \mathrm{DMT}(b^{(t')})) 这一部分的点乘是单次 O(2^n),总共 O(n2^n) 的。

最后对每个 c^{(i)} 各做一次 \mathrm{iDMT},单次 O(n2^n),总共 O(n^22^n)

总复杂度为 O(n^22^n)

法二

需要用到占位集合幂级数。这个东西也很有用,对集合幂级数的 exp 和 ln 都有用。

下面给出它的定义。

设有一个长度 2^n 的序列 a_I。定义一个占位集合幂级数 A[z](x_1\cdots x_n)=\sum_{I}a_Ix^Iz^{|I|},普通集合幂级数就是 A[1](x_1\cdots x_n)注意 z 对应的是数字而不是集合。

占位集合幂级数也可以定义乘法:x^Iz^u\cdot x^Jz^v=x^{I\cup J}z^{u+v}。对与卷积和异或卷积也是类似的。不过这里要用来解决子集卷积就携程或卷积的形式。

我们定义 x^Iz^u 是合法项,当且仅当 |I|=u。容易发现 I,J 不相交 \iff x^Iz^{|I|}\cdot x^Jz^{|J|} 为合法项。

简写 A[z](x_1\cdots x_n)A[z]

引理:设 c=a*b(子集卷积),a\rightarrow A[z],b\rightarrow B[z],c\rightarrow C[z]。有:A[z]B[z] 保留合法项后恰为 C[z]

证明比较显然。不合法项在子集卷积中是不会出现的。

保留合法项是很容易的,只要 O(2^n) 遍历每一项判断即可。那么问题变成求 A[z]B[z]

法 1

A^{(i)}a^{(i)}集合幂级数。这个 a^{(i)} 就是在法一中按集合大小分组的 a^{(t)}B^{(i)} 类似定义。

\begin{cases} A[z]=A^{(0)}+A^{(1)}z+\cdots +A^{(n)}z^n\\ B[z]=B^{(0)}+B^{(1)}z+\cdots +B^{(n)}z^n\\ \end{cases}

枚举 i,对于 A[z]\cdot B[z]z^i 项,包含它的项为 \sum_{t+t'=i}A^{(t)}z^t\cdot B^{(t')}z^{t'}=\sum_{t+t'=i}(A^{(t)}B^{(t')})z^i

而对于 A^{(t)}B^{(t')} 的处理,我们和上面一样进行 \mathrm{iDMT},\mathrm{DMT},利用 \mathrm{DMT} 的线性变换性质把集合幂级数的相乘转化为向量的点乘,从而优化复杂度。

具体细节略去。这种方法本质和上面是相同的。不过是引入了 z 作为主元

法 2

此种方法不同,它并非按 z 分类,而是把 z 看作参数

A[z],B[z] 当作普通的集合幂级数,但是每一项的系数不是一个数,而是一个关于 z 的多项式。

具体而言,A[z]=\sum_{I}(a_Iz^{|I|})x^IB[z]=\sum_{I}(b_Iz^{|I|})x^I。这里 (a_Iz^{|I|})(b_Iz^{|I|}) 看作系数。

计算 A[z]B[z]=\mathrm{iDMT}(\mathrm{DMT}(A[z])\cdot \mathrm{DMT}(B[z])),现在的问题是对于系数是多项式的时候 \mathrm{DMT} 是怎么定义的。

其实没有区别,\overline{a}_I=\sum_{J\subseteq I}a_I,不过前缀和变成了多项式的和。子集反演后也是一样。

复杂度呢?

普通数组做 \mathrm{DMT}O(n2^n) 的,但是现在系数是一个长度为 n 的多项式,所以复杂度是 O(n^22^n) 的。

A[z],B[z] 各自求 \mathrm{DMT},复杂度 O(n^22^n);让两个结果点乘,复杂度 O(2^n);对点乘的结果再做 \mathrm{iDMT},复杂度 O(n^22^n)。注意这里 \mathrm{iDMT} 的复杂度也是平方,因为 \mathrm{iDMT} 也要对多项式运算。