• 快速沃尔什变换&快速莫比乌斯变换小记


    u1s1 距离省选只剩 5 days 了,现在学新算法真的合适吗((

    位运算卷积

    众所周知,对于最普通的卷积 \(c_i=\sum\limits_{j+k=i}a_jb_k\)\(a_jb_k\) 的贡献累加到 \(c_{j+k}\) 上,因此这种卷积又被称为加法卷积。

    但是对于某些卷积,\(a_jb_k\) 的贡献就不是累加到 \(j+k\) 上了,有一类卷积,\(a_jb_k\) 的贡献会累加到 \(j\otimes k\) 上,其中 \(\otimes\) 是某种位运算,即 \(\&,|,\oplus\) 中的一个。

    位运算卷积的名称视 \(\otimes\) 运算而定,例如卷积 \(c_i=\sum\limits_{j|k=i}a_jb_k\) 被称为 \(\text{or}\) 卷积。

    快速沃尔什变换(FWT)

    直接求两序列的位运算卷积显然是平方的,而快速沃尔什变换(FWT)可在 \(n\log n\) 的复杂度内求出两序列的位运算卷积。

    回顾一下我们使用 FFT 求两序列加法卷积的过程,我们构造了一个序列 \(\hat{a}\) 表示序列 \(a\) 的点值序列,那么根据 \(\hat{c}_i=\hat{a}_i\times\hat{b}_i\) 可在线性时间由 \(\hat{a},\hat{b}\) 求出 \(\hat{c}\),再在 \(n\log n\) 的时间内实现 \(a\to\hat{a},b\to\hat{b}\) 以及 \(\hat{c}\to c\) 即可在 \(n\log n\) 的时间内求出两序列的卷积。

    考虑将 FFT 求加法卷积的过程迁移到位运算卷积上,我们希望能对已知序列 \(a\) 构造出一个新的序列 \(\text{FWT}(a)\),满足 \(\text{FWT}(a)_i\times\text{FWT}(b)_i=\text{FWT}(c)_i\),并且能够在 \(n\log n\) 的时间内实现 \(a\to\text{FWT}(a),b\to\text{FWT}(b)\) 以及 \(\text{FWT}(c)\to c\),这样就可以实现 \(n\log n\) 求出 \(c_i\)

    大体思路就是这样。不过对于不同的位运算卷积,显然我们构造的 \(\text{FWT}(a)\) 也是不同的,所以还是具体情况具体分析罢。

    \(\text{or}\) 卷积

    我们构造 \(\text{FWT}(a)_i=\sum\limits_{j|i=i}a_j\)

    那么有:

    \[\begin{aligned}\text{FWT}(c)_i&=\sum\limits_{x|i=i}c_x\\&=\sum\limits_{x|i=i}\sum\limits_{j|k=x}a_jb_k\\&=\sum\limits_{(j|k)|i=i}a_jb_k\\&=\sum\limits_{j|i=i\land k|i=i}a_jb_k\\&=\sum\limits_{j|i=i}a_j\sum\limits_{k|i=i}b_k\\&=\text{FWT}(a)_i\text{FWT}(b)_i\end{aligned} \]

    接下来考虑怎样求 \(\text{FWT}(a)_i\),其实仔细观察这个求和公式,不难发现如果我们将 \(i\) 的二进制表示看作一个集合,那么 \(\text{FWT}(a)_i\) 实际上就是所有 \(j\subseteq i\)\(a_j\) 的总和,这显然可以用 SOS DP(高维前缀和)求出,复杂度 \(n\log n\),实际上这就是 FMT(快速莫比乌斯变换)的基本思想,将在讲解 FMT 的部分提到。

    考虑怎样用类似于 FFT 的分治方法求解这个东西,记 \(m=\log_2n\)。我们考虑将 \(a_0,a_1,a_2,\cdots,a_{2^m-1}\) 分成两部分,一部分最高位为 \(0\),记作序列 \(b_0\),另一部分最高位为 \(1\),记作序列 \(b_1\),那么显然 \(b_{0,i}=a_i,b_{1,i}=a_{i+2^{m-1}}(i\in[0,2^{m-1}-1])\),我们假设已经知道了 \(\text{FWT}(b_{0}),\text{FWT}(b_{1})\) 的值怎样计算 \(\text{FWT}(a)\),显然对于 \(i<2^{m-1}\)\(\text{FWT}(b_1)_j\) 不可能对 \(\text{FWT}(a)_i\) 产生任何影响,故 \(\text{FWT}(a)_i=\text{FWT}(b_0)_i\),而对于 \(i\ge 2^{m-i}\),所有满足 \(j|i=i\)\(j\) 可以分为两类,\(j\)\(2^{m-1}\) 位为 \(0\) 和为 \(1\),若 \(j\)\(2^{m-1}\) 位为 \(0\),那么必然有 \(j|(i-2^{m-1})=i-2^{m-1}\),产生的贡献为 \(\text{FWT}(b_0)_{i-2^{m-1}}\),同理若 \(j\)\(2^{m-1}\) 位为 \(1\),那么 \((j-2^{m-1})|(i-2^{m-1})=i-2^{m-1}\)\(\text{FWT}(b_1)_{i-2^{m-1}}\),故 \(\text{FWT}(a)_i=\text{FWT}(b_0)_{i-2^{m-1}}+\text{FWT}(b_1)_{i-2^{m-1}}\),递归求解即可。当然也有不递归的写法——可以像 FFT 那样从长度为 \(2\) 的区间开始求解,接下来求解长度为 \(4\) 的区间,以此类推,复杂度 \(n\log n\)

    void FWTor(int *a,int len){
    	for(int i=2;i<=len;i<<=1)
    		for(int j=0;j<len;j+=i)
    			for(int k=0;k<(i>>1);k++)
    				a[(i>>1)+j+k]+=a[j+k];
    }
    

    接下来考虑怎样通过 \(\text{FWT}(a)_i\) 反解 \(a_i\),其实就是上述过程的逆操作罢了。记数组 \(b\) 为我们待还原的数组,初始 \(b_i=\text{FWT}(a)_i\)。我们从低到高位考虑,考虑最低位为 \(1\) 的所有 \(i\),显然对于所有 \(j|i=i\)\(j\),它的最低位有可能是 \(0\),也有可能是 \(1\),而我们不希望最低位为 \(0\) 的部分累加入 \(b_i\),那么我们就令 \(b_i\leftarrow b_i-b_{i-1}\),显然 \(i-1\)\(i\) 只有最后一位不同,故此时 \(b_{i-1}\) 就是所有满足 \(j|i=i\),并且 \(j\) 的最低位为 \(0\)\(a_j\) 之和,因此进行一遍这个操作后,得到的 \(b_i\) 就是所有满足 \(j|i=i\),并且 \(j\) 的最低位为 \(1\)\(a_j\) 之和。我们再对倒数第二位为 \(1\)\(j\) 执行类似的操作,只不过把 \(b_i\leftarrow b_i-b_{i-1}\) 改为 \(b_i\leftarrow b_{i-2}\),仿照上述推理过程可知此时 \(b_i\) 就是所有满足 \(j|i=i\),并且 \(j\) 的最后两位与 \(i\) 的最后两位相同的 \(a_j\) 之和。以此类推,最终得到的 \(b_i\) 就是我们想要的 \(a_i\) 了。

    void IFWTor(int *a,int len){
    	for(int i=2;i<=len;i<<=1)
    		for(int j=0;j<len;j+=i)
    			for(int k=0;k<(i>>1);k++)
    				a[(i>>1)+j+k]-=a[j+k];
    }
    

    显然两份代码可以合并:

    void FWTor(int *a,int len,int type){
    	for(int i=2;i<=len;i<<=1)
    		for(int j=0;j<len;j+=i)
    			for(int k=0;k<(i>>1);k++)
    				a[(i>>1)+j+k]+=type*a[j+k];
    }
    

    \(\text{and}\) 卷积

    其实与 \(\text{or}\) 卷积差不多罢……

    只不过把 \(\text{FWT}(a)_i\) 的定义改为 \(\text{FWT}(a)_i=\sum\limits_{j\&i=i}a_j\),仿照 \(\text{or}\) 卷积的推理过程可知这里 \(\text{FWT}(c)_i=\text{FWT}(a)_i\times \text{FWT}(b)_i\) 同样成立。

    求解过程也异常类似,只不过把 \(a_{i+2^{m-1}}\leftarrow a_{i}+a_{i+2^{m-1}}\) 改为 \(a_{i}\leftarrow a_{i}+a_{i+2^{m-1}}\),因为集合意义下的 \(\&\) 运算可以看作对补集的 \(|\) 运算。时间复杂度同样是 \(n\log n\)

    void FWTand(int *a,int len,int type){
    	for(int i=2;i<=len;i<<=1)
    		for(int j=0;j<len;j+=i)
    			for(int k=0;k<(i>>1);k++)
    				a[j+k]+=type*a[(i>>1)+j+k];
    }
    

    \(\text{xor}\) 卷积

    zszz,\(\text{and}\) 运算可以看作集合取交,\(\text{or}\) 运算可以看作集合取并。

    \(\text{xor}\) 运算……似乎没法直接与集合建立起联系啊……

    这也就是为什么 \(\text{xor}\) 卷积的推导过程比 \(\text{and}\)\(\text{or}\) 卷积要复杂不少。

    我们考虑函数 \(f(i,j)=(-1)^{|i\cup j|}\),i.e.,如果 \(i\&j\) 二进制下 \(1\) 的个数为奇数则返回 \(-1\),否则返回 \(1\)

    显然 \(f\) 函数有一个很重要的性质:\(f(i,j)f(i,k)=f(i,j\oplus k)\),因为显然 \(i\cup(j\oplus k)=(i\cup j)\oplus (i\cup k)\),故 \(f(i,j\oplus k)=(-1)^{|i\cup(j\oplus k)|}=(-1)^{|(i\cup j)\oplus (i\cup k)|}=(-1)^{|i\cup j|}(-1)^{|i\cup k|}=f(i,j)f(i,k)\)

    因此我们考虑记 \(\text{FWT}(a)_i=\sum\limits_{j}f(i,j)a_j\),那么有:

    \(\begin{aligned}\text{FWT}(c)_i&=\sum\limits_{x}c_xf(i,x)\\&=\sum\limits_{x}\sum\limits_{j\oplus k=x}a_jb_kf(i,x)\\&=\sum\limits_{x}\sum\limits_{j\oplus k=x}a_jb_kf(i,j)f(i,k)\\&=\sum\limits_{j}a_jf(i,j)\sum\limits_{k}b_kf(i,k)\\&=\text{FWT}(a)_i\text{FWT}(b)_i\end{aligned}\)

    (其实通过上面的推导过程可以发现所有位运算卷积的 \(\text{FWT}(a)_i\) 都可以写成形如 \(\sum\limits_{j}f(i,j)a_j\) 的形式,其中 \(f(i,j)\) 需满足 \(f(i,j)f(i,k)=f(i,j\otimes k)\)\(\otimes\) 为对应的位运算)

    按照套路接下来就是怎样求解 \(\text{FWT}(a)_i\) 的问题了,我们还是考虑将 \(a\) 序列按下标的最高位分为两个序列,最高位为 \(0\) 的序列记作 \(b_0\),最高位为 \(1\) 的序列记作 \(b_1\),那么:

    • 对于 \(i\le 2^{m-1}-1\),显然有 \(j\&i=(j-2^{m-1})\&i(j\ge 2^{m-1})\),故下式成立:

      \[\begin{aligned}\text{FWT}(a)_i&=\sum\limits_{j=0}^{2^{m-1}-1}f(i,j)b_j+\sum\limits_{j=2^{m-1}}^{2^m-1}f(i,j)b_j\\&=\sum\limits_{j=0}^{2^{m-1}-1}f(i,j)b_{0,j}+\sum\limits_{j=2^{m-1}}^{2^m-1}f(i,j-2^{m-1})b_{1,j-2^{m-1}}\\&=\text{FWT}(b_0)_j+\text{FWT}(b_1)_j\end{aligned} \]

    • 对于 \(i\ge 2^{m-1}\),类似地有 \(j\&i=\begin{cases}(j-2^{m-1})\&i&(j<2^{m-1})\\(j-2^{m-1})\&i+2^{m-1}&(j\ge2^{m-1})\end{cases}\),故我们有:

      \[\begin{aligned}\text{FWT}(a)_i&=\sum\limits_{j=0}^{2^{m-1}-1}f(i-2^{m-1},j)b_{j}+\sum\limits_{j=2^{m-1}}^{2^m-1}f(i,j)b_j\\&=\sum\limits_{j=0}^{2^{m-1}-1}f(i-2^{m-1},j)b_{0,j}+\sum\limits_{j=2^{m-1}}^{2^m-1}-f(i-2^{m-1},j-2^{m-1})b_{1,j-2^{m-1}}\\&=\text{FWT}(b_0)_j-\text{FWT}(b_1)_j\end{aligned} \]

    仿照 FWTor/FWTand 的套路计算一下即可。

    至于逆操作……一个道理,把所有操作还原回去即可。

    void FWTxor(int *a,int len,int type){
    	for(int i=2;i<=len;i<<=1)
    		for(int j=0;j<len;j+=i)
    			for(int k=0;k<(i>>1);k++){
    				int X=a[j+k],Y=a[(i>>1)+j+k];
    				if(type==1) a[j+k]=X+Y,a[(i>>1)+j+k]=X-Y;
    				else a[j+k]=(X+Y)/2,a[(i>>1)+j+k]=(X-Y)/2;
    			}
    }
    

    快速莫比乌斯变换(FMT)

    快速沃尔什变换可以在 \(n\log n\) 的时间内求出两个序列的位运算卷积,事实上,快速莫比乌斯变换也有着类似的功效,只不过它的局限性比快速沃尔什变换稍微大一点——它只能用来求两个序列的 \(\text{or}\) 卷积和 \(\text{and}\) 卷积,复杂度同 FWT,\(\text{xor}\) 卷积不在其能搞定的范围内,也就是说 FWT 是严格强于 FMT 的,不过 FMT 的优点在于它的理解难度比 FWT 小。

    FMT 与 FWT 最大的不同点在于它们的切入点不同,FWT 是直接从位运算的角度优化上述公式的计算,而 FMT 则是从集合的角度切入的,也就是说我们可以把所有 \([0,2^m)\) 的数看作一个 \(\{1,2,3,4,\cdots,n\}\) 的子集,对于 \(i\in[0,2^m),j\in[0,m-1]\),若 \(i\) 的第 \(2^j\) 位为 \(1\),则 \(j+1\)\(i\) 表示的集合中,反之 \(j+1\) 不在 \(i\) 表示的集合中。在下文中为了表述方便,记 \(i\cup j=i|j,i\cap j=i\&j,i\subseteq j=[i|j=j]\)

    \(\text{or}\) 卷积为例,我们不直接求出 \(c_i\),instead 我们记 \(d_i=\sum\limits_{j\subseteq i}c_j\),也就是所有包含于 \(i\)\(j\)\(c_j\) 之和,显然求出了 \(d_i\) 只需高维差分一下即可求出 \(c_j\),接下来考虑怎样求 \(d_i\),考虑什么样的 \(j,k\) 满足 \(a_jb_k\) 会对 \(d_i\) 产生贡献,显然 \(a_jb_k\) 会直接对 \(c_{j\cup k}\) 产生贡献,而 \(c_{j\cup k}\)\(d_i\) 产生贡献的充要条件是 \((j\cup k)\subseteq i\),故 \(a_jb_k\)\(d_i\) 产生贡献的充要条件是 \((j\cup k)\subseteq i\),而显然 \((j\cup k)\subseteq i\leftrightarrow j\subseteq i\land k\subseteq i\),此时 \(j,k\) 就可以独立考虑了,记 \(s_i=\sum\limits_{j\subseteq i}a_j,t_i=\sum\limits_{k\subseteq i}b_k\),那么有 \(d_i=s_it_i\)\(s_i,t_i\) 显然可以用一遍高维前缀和求出,复杂度 \(n\log n\)

    至于 \(\text{and}\) 卷积,只需把前缀和改为后缀和即可,复杂度同样 \(n\log n\)

    u1s1 FMT 与 FWT 其实根本没有本质区别……

    子集卷积

    大概就是对于序列 \(a,b\),求满足 \(c_i=\sum\limits_{j|k=i\land j\&k=0}a_jb_k\) 的序列 \(c\)

    首先可以肯定的是用 FWTor 只能满足 \(j|k=i\),无法满足 \(j\&k=0\)

    不过有一个显然的 observation 就是 \(j\&k=0\) 等价于 \(|j|+|k|=|j\cup k|\)(这里是将所有非负数看作一个集合,\(|x|\) 即为集合 \(x\) 的大小)。

    因此我们考虑将原来的一维数组变为二维数组,即 \(a_i=x\rightarrow a_{|i|,i}=x\),对于 \(i\in[0,m],j\in[0,2^m)\),若 \(i\ne|j|\)\(a_{i,j}=0\)

    那么显然 \(c_i=\sum\limits_{x+y=|i|}\sum\limits_{j|k=i}a_{x,j}b_{y,k}\)

    由于 \(|i|\) 很小,故考虑直接枚举 \(x\),后面那坨东西就是一个标准的 FWTor 的形式了,FWT 优化一下即可。

    时间复杂度 \(2^nn^2\)

    例题:

    先咕着,省选后再来填。

    upd on 2021.4.17:来填坑了,我真是个鸽子(伦敦雾

    CF582E Boolean Function

    注意到只有四个变量 \(A,B,C,D\),也就是说可能的变量取值 \((a_i,b_i,c_i,d_i)\) 只有 \(2^4=16\) 种,我们考虑将每种可能的 \((a_i,b_i,c_i,d_i)\) 编号 \(0\sim 15\),然后再用一个 \(2^{16}\) 的二进制数表示每种 \((a_i,b_i,c_i,d_i)\) 的取值。这样就可以 \(dp\) 了,建出表达式树,然后记 \(dp_{i,j}\) 表示执行完 \(i\) 子树中的运算后,每种 \((a_i,b_i,c_i,d_i)\) 的取值压成二进制数后状态为 \(j\) 的方案数,转移就考虑该节点上的运算是什么,如果不是 and 那就把左右儿子的 \(dp\)FWTor 一下累加到当前节点的 \(dp\) 值上,如果不是 or 那就把左右儿子的 \(dp\)FWTand 一下累加到当前节点的 \(dp\) 值上即可。复杂度 \(|s|\times 2^{16}\times 16\)

    CF662C Binary Table

    我们枚举行翻转的状态 \(S\),记第 \(i\)1 的状态表示的二进制数为 \(x_i\),那么翻转状态为 \(S\) 时候最少 \(1\) 的个数为 \(ans_S=\sum\limits_{i=1}^m\min(\text{bitcnt}(x_i\oplus S),n-\text{bitcnt}(x_i\oplus S))\),如果我们记 \(t_x=\min(\text{bitcnt}(x),n-\text{bitcnt}(x))\),那么 \(ans_S=\sum\limits_{i=m}t_{x_i\oplus S}\),如果我们再记 \(c_v\)\(\sum [x_i=v]\),那么上式又可写作 \(\sum\limits_{x\oplus y=S}c_xt_y\),FWTxor 一下即可

    CF1119H Triple

    immortal tea,题解

    P4221 [WC2018]州区划分

    首先我们预处理出 \(ok_S\) 表示集合 \(S\) 是否可以单独划分为一个州,这个可以用类似于欧拉回路的方法解决,这里就不赘述了。

    接下来考虑 \(dp\)\(dp_S\) 表示考虑了集合 \(S\) 的答案,那么显然有 \(dp_S=\dfrac{1}{sum_S}\sum\limits_{T\subseteq S}sum_Tok_Tdp_{S-T}\),暴力枚举 \(T\) 复杂度是 \(3^n\),无法通过。不过这个式子显然可以写成子集卷积的形式,子集卷积优化一下即可,时间复杂度即可降到 \(n^22^n\)

    感觉这题放在 WC 有点过水了……

    CF1336E1 Chiori and Doll Picking (easy version)

    首先看到“本质不同异或和”可以想到线性基,求出原序列的线性基,设线性基大小为 \(k\),那么对于所有能表示成线性基中元素的线性组合的所有数 \(v\),都恰有 \(2^{n-k}\) 个子序列满足其异或和为 \(k\),这样我们问题的规模就由 \(2^n\) 降到了 \(2^k\)

    可这样还是过不了啊……不难发现这题数据范围给得很玄妙,一脸折半搜索的亚子。因此考虑折半搜索,我们考虑将线性基分成“高位”和“低位”两部分,暴力指数级别地枚举高位和低位选了哪些数,然后 FWTxor 合并一下即可,复杂度 \(2^{\lceil\frac{m}{2}\rceil}m^2\)

    CF1326F2 Wise Men (hard version)

    immortal tea,题解

    CF914G Sum the Fibonacci

    首先开一个桶 \(cnt_i\) 表示有多少个 \(s_j=i\),然后通过预处理出:

    • \(c1_i\) 表示有多少对 \(a,b\) 满足 \(s_a\&s_b=0,s_a|s_b=i\)
    • \(c2_i\) 表示有多少对 \(a,b\) 满足 \(s_a\oplus s_b=i\)

    前者可以通过子集卷积求出,后者可以通过 FWTxor 求出。

    然后再记 \(s1_i=c1_i\times f_i,s2_i=cnt_i\times f_i,s3_i=c2_i\times f_i\),把 \(s1,s2,s3\) FWTand 起来得到 \(c\),然后把 \(c_{2^k}\) 累加起来即是答案。正确性显然。

    UOJ 310【UNR #2】黎明前的巧克力

    题目等价于在 \(S\) 中选择两个不相交的子集 \(S_1,S_2\),满足 \(S_1\cup S_2\) 中所有元素的异或和为 \(0\)

    考虑 \(dp\)\(dp_{i,j}\) 表示考虑了前 \(i\) 个巧克力,选择的子集异或和为 \(j\)

    考虑转移,如果 \(i\) 不选,那么 \(dp_{i,j}\leftarrow dp_{i-1,j}\),如果 \(i\) 选,那么 \(i\) 可以选择放入 \(S_1,S_2\) 的任意一者中,即 \(dp_{i,j}\leftarrow 2·dp_{i-1,j\oplus a_i}\),暴力 \(dp\) 显然会 TLE,注意到本题这个东西与 CF1119H 有点像,按照那题的套路优化一下即可。

    鸽了一个月终于 C7H16 了(

  • 相关阅读:
    kafka学习笔记(六)kafka的controller模块
    腾讯蓝鲸使用笔记
    kafka学习笔记(五)kafka的请求处理模块
    kafka学习笔记(四)kafka的日志模块
    kafka学习笔记(三)kafka的使用技巧
    kafka学习笔记(二)kafka的基本使用
    kafka学习笔记(一)消息队列和kafka入门
    大数据安全与RANGER学习和使用
    10-Linux 基本指令
    09-迭代器、模块
  • 原文地址:https://www.cnblogs.com/ET2006/p/fwtfmt.html
Copyright © 2020-2023  润新知