总结一下有关子集变换的笔记。

约定:

  1. 接下来我们讨论的序列一般指长度为,下标从开始的序列。例如
  2. 集合幂级数其实可以理解为以集合为下标的序列。本质上就是普通的序列,只不过是用集合的运算表示位运算。本文中的序列可能指集合幂级数,也可能指普通序列,具体见上下文或者使用的记号。
  3. 集合占位多项式可以理解为是一个序列,序列中的每个元素都是一个多项式。

快速莫比乌斯变换

子集与

考虑形式化的问题:对于两个序列,我们希望求序列

用集合幂级数的语言,我们想求的是

其中是集合。接下来的问题与过程形式都使用集合来展现。

考虑做一个高维后缀和的变换。对于集合幂级数定义

那么我们对同时做高维后缀和变换得到:

也就是说如果我们能快速计算的高维后缀和,就可以快速得到的高维后缀和

在求出了后,我们希望还原成,可以用容斥得到

但其实没必要这么麻烦。我们可以直接倒着写高维后缀和,就可以逆变换回去。

高维后缀和可以写成

for(int i=0;i<n;i++){
    for(int j=0;j<(1<<n);j++)
        if((j&(1<<i))==0)
            s[j]+=s[j+(1<<i)];
}

高维后缀差分(逆变换):

for(int i=0;i<n;i++){
    for(int j=0;j<(1<<n);j++)
        if((j&(1<<i))==0)
            s[j]-=s[j+(1<<i)];
}

时间复杂度,空间复杂度

子集或

对于序列,我们想求

类似的,我们这次定义一个高维前缀和:

则可以推出

于是类似地使用高维前缀和与高维前缀差分即可。

扩域情况

对于集合幂级数而言,每一个元素只有选或者不选两种情况。换言之,二进制数的每个位只有或者两种值。而对于可重集,即进制数来说,也是可以定义集合幂级数以及高维前(后)缀变换的。

以子集与为例,高维后缀和变换可以一般地表示为

相当于与运算;相当于后缀和操作。

分治多项式乘法

为了理解 FWT 的 xor 变换在干啥,我们先引入分治多项式乘法。

我们有两个定义在加法乘法环下的次多项式,我们想求。设

假定的幂。则我们有

而我们知道

因此可以只算的乘法,递归下去。时间复杂度

异或卷积

异或卷积问题:对于序列,我们要求

表示位异或运算。

我们首先介绍分治异或卷积算法,然后介绍广泛使用的快速沃尔什变换。了解分治异或卷积算法有利于理解快速沃尔什变换。

分治异或卷积

由于异或具有结合律、交换律,对加法的分配律,则我们定义一个指数通过异或运算合并的多项式来表示该序列:

可以理解为是“异或生成函数”。类似地我们定义出以及,那么有。这里的乘法定义为

为了计算上述异或乘法,我们将多项式前后拆成两半。设

则使用分治可以得到

这里有个特殊之处:由于下标的异或运算使得项的指数被消掉了,因此我们实际上只用递归算两次多项式乘法。我们设

于是可以得到

那么

时间复杂度就是

事实上,与卷积和或卷积也可以使用分治多项式乘法去理解。

快速沃尔什变换

快速沃尔什变换(Fast Walsh–Hadamard transform,FWT or FWHT)1是用于快速计算一个序列的沃尔什变换的算法。在算法竞赛领域中,大多数时候 FWT 的作用是快速计算异或卷积。

FWT 的本质是把变成了

写成集合幂级数的形式就是

不过直接抛出 FWT 的定义并不能帮助我们理解为什么 FWT 可以加速异或卷积的计算。

通过分治异或卷积理解

为此我们沿用上文中分治异或卷积的描述。

首先我们要更改分治异或卷积的执行顺序,更准确地说是拆分

分治异或卷积的优化用一句话概括就是:将计算归约到计算两个长度减半的异或卷积。

在实现的时候设表示将做异或卷积。

我们不妨考虑将变成,将变成。对于这两个新的多项式:

  • 首先我们将其前半部分做卷积,后半部分做卷积,即递归调用
  • 经过上一步我们计算出了,然后再根据,还原出的系数表示即可。

考虑第一步和第二步本质上做了什么事情。他们其实执行的计算是差不多的,只不过第二步需要除以而第一步不需要。且第一步我们需要同时变换,而在第二步里其实表示的是同一个多项式的高低位。

因此我们有一个想法:我们将第一步的过程和第二步的过程拆开,再将第一步中对的变换过程拆开!

换言之我们的操作过程变成:

  • 先分别递归变换得到
  • 这时里存的分别是个单项式(只有常数项的多项式)。因此我们直接将两者点乘得到
  • 然后递归地自底向上执行第二步的过程,将转化为最后的

由于这三个过程彼此不相关,只要顺序不要乱就行,因此拆开的正确性有保证。

接下来我们要理解对的变换是 FWT 变换。

为例,我们分析对的变换:将变成,然后前后两半递归变换。

,那么我们对于分析的值是如何得到的。

的情况为例,我们用绿色线表示系数为正,红色线表示系数为负,画出从变换为的示意图:

Subset-Transform-1

考虑的贡献。那么我们只需要考虑贡献的系数是还是即可,这显然由从路径上的红色箭头数量的奇偶性决定。为此我们枚举考虑二进制下第位:

  • 如果两者第位是相同的,那么走的就是水平方向的,这时是否为红色箭头取决于这一位是否为
  • 如果两者第位不同,那么得走斜着的箭头,而斜着的箭头是不存在红色的。

综上所述,系数即为。因此,这恰好就是 FWT 变换的定义。

代码实现

FWT 和 IFWT 的递归过程为

异或卷积则是 FWT 后直接做乘法:

事实上,。因此非递归版本的 IFWT 也可以直接在最后除掉一个

模板代码

Trick

有一个小 Trick 就是,如果我们修改,那么等变换回去后,所有项都被增加一个常量(这个常量不一定等于我们修改的差量)。原因如上所述,的贡献永远是正的。

这个性质可以用来解一些要求的题,那么我们给随机一个值,变换回去后把所有项都减掉即可。

子集卷积

我们考虑这样一个问题:求不相交的或卷积:

这个不好求。我们设一个集合占位多项式:

左右两个定义是等价的。

则可以得到

把这个逆变换得到

这时你会发现,当时有。因此我们取就是答案。

时间复杂度为

在实现的时候有一个技巧,就是在暴力卷积的时候要稍微调整循环顺序,不要一列一列访问内存,不然常数极大。

代码

HDU 5823

首先显然色数是的,因为你每个点染不同颜色一定是成立的。

而显然每种颜色可以染一个独立集。设表示点集是否是一个独立集,表示点集能否使用种颜色染。则

但事实上不是必要条件,它不会影响最优解。因此这就是一个子集或卷积,需要做次,每次卷完要回来转成艾弗森括号运算值。总复杂度

代码

CF1034 E

可以使用上述子集卷积做法,复杂度,在这道题中是不能通过的。

,还原一下有

注意到这个多项式的最低次项的指数是。而我们要的就是所有的和(即)。

,这题要求对取模。

那么我们可以直接把代入这个多项式,因为是对取模,因此除了常数项之外的其他项的贡献都是。实现的时候开 LL 暴力右移即可。

代码

HDU 5330

这道题虽然名称上是状圧 DP,但在 DP 的过程中是按维度考虑,其实是在做一个高维前缀和的过程。这样就更容易理解它 DP 的含义了。

具体的 DP 方法可以见注释。

代码

AGC034 F

表示从变到的期望:

。注意到这是一个异或卷积的形式(),则这个等式可以被表示为

,不满足和式,因此表示为状态。

由于,而在上述异或卷积中每个都和所有的乘起来贡献了一次。因此

于是得到

我们想办法把所有消掉:

于是我们 FWT 之后计算,然后再转回来即可。因为最后的答案是强制,所以在做的时候可以直接乘上逆元。最后使用 FWT 本质的 Trick 即可。

时间复杂度

代码

1. https://en.wikipedia.org/wiki/Fast_Walsh%E2%80%93Hadamard_transform