FWT 从入门到拍案叫绝

二进制卷积太美妙了。

高维前缀和

本质上是 OR 卷积。

https://www.cnblogs.com/heyuhhh/p/11585358.html

这个写得挺好。我觉得应该还算是很好理解吧?

位运算卷积

快速沃尔什变换 (FWT)

\(C_i=\sum\limits_{j\oplus k}A_jB_k\),则 \(C\)\(A,B\) 的位运算 \(\oplus\) 的卷积。

我们希望对 \(A,B,C\) 进行 FWT 取得 \(f(A),f(B),f(C)\),然后点积计算 \(f(C)=f(A)\cdot f(B)\),再对 \(f(C)\) 进行一次 IFWT 取得 \(C\) 以控制时间复杂度。

考虑构造 FWT 的变换方案。令 \(c(i,j)\) 表示 \(A_j\)\(f(A)_ i\) 的贡献系数那么有 \(f(A)_ i=\sum\limits_ {i=0}^{n-1} c(i,j)A_j\)。当然绝大多数 FWT 题都满足 \(n\)\(2\) 的幂次,否则不方便我们之后的操作。不过如果真的 \(n\neq 2^k\),其实在后面补足 \(0\) 就行了。

然后进行一个狠狠的等价变换:

\[\begin{aligned} f(C)_ i&=f(A)_ i\cdot f(B)_ i\\ \sum\limits_ {j=0}^{n-1} c(i,j)C_j&=[\sum\limits_ {j=0}^{n-1} c(i,j)A_j][\sum\limits_ {j=0}^{n-1} c(i,j)B_j]\\ \sum\limits_ {j=0}^{n-1} c(i,j)C_j&=\sum\limits_ {j=0}^{n-1}\sum\limits_ {k=0}^{n-1} c(i,j)c(i,k)A_jB_k\\ \end{aligned} \]

对左边根据定义式进行一个代换:\(C_ i=\sum\limits_{p\oplus q=i} A_pB_q\),那么有 \(f(C)_ i=\sum\limits_{j=0}^{n-1} c(i,j)\sum\limits_{p\oplus q=j} A_pB_q\)

回代,等价变换。鉴于 \(n\) 总是等于 \(2^k\),可以再更换枚举方式:

\[\begin{aligned} \sum\limits_{j=0}^{n-1} c(i,j)\sum\limits_{p\oplus q=j} A_pB_q &=\sum\limits_ {j=0}^{n-1}\sum\limits_ {k=0}^{n-1} c(i,j)c(i,k)A_jB_k\\ \sum\limits_{j=0}^{n-1}\sum\limits_{p\oplus q=j} c(i,p\oplus q)A_pB_q &=\sum\limits_ {j=0}^{n-1}\sum\limits_ {k=0}^{n-1} c(i,j)c(i,k)A_jB_k\\ \sum\limits_{p=0}^{n-1}\sum\limits_{q=0}^{n-1} c(i,p\oplus q)A_pB_q &=\sum\limits_ {j=0}^{n-1}\sum\limits_ {k=0}^{n-1} c(i,j)c(i,k)A_jB_k\\ \end{aligned} \]

统一字母,发现得到关系 \(c(i,j)c(i,k)=c(i,j\oplus k)\)。利用真值表和高中数学抽象函数求值的方法可以取得 \(c([0,1],[0,1])\) 也就是 \(c\) 函数的位矩阵。

为了方便之后分治卷积的操作并满足上述关系,我们考虑让 \(c(i,j)\)\(i>1\lor j>1\) 时每一位都独立开。令 \(i_t\) 表示 \(i\) 二进制下第 \(t\) 位,\(j_t\) 表示 \(j\) 二进制下第 \(t\) 位,我们使 \(c(i,j)=\prod c(i_t,j_t)\)。回代可以发现,由于位矩阵早已满足关系,所以对于每个 \(t\) 都有 \(c(i_t,j_t)c(i_t,k_t)=c(i_t,j_t\oplus k_t)\),再把每一位合并在一起从而得到与 \(c(i,j)c(i,k)=c(i,j\oplus k)\) 等价。

假设我们已经取得了所有 \(c\),下一步就是猛算 \(f(A)_i=\sum\limits_{j=0}^{n-1} c(i,j)A_j\),当然这还是 \(\mathcal O(n^2)\) 的。

鉴于 \(n\) 总是等于 \(2^k\),考虑按二进制下最高位的值分治,再利用 \(c\) 每一位的独立性拆分进行变换。记 \(i_0\)\(i\) 的最高位:

\[\begin{aligned} f(A)_i&=\sum\limits_{j=0}^{n-1} c(i,j)A_j\\ &=[\sum\limits_{j=0}^{\frac{n}{2}-1} c(i,j)A_j]+[\sum\limits_{j=\frac{n}{2}}^{n-1} c(i,j)A_j]\\ &=[c(i_0,0)\sum\limits_{j=0}^{\frac{n}{2}-1} c(i',j')A_j]+[c(i_0,1)\sum\limits_{j=\frac{n}{2}}^{n-1} c(i',j')A_j]\\ \end{aligned} \]

考虑 \(A_0\) 表示前半部分的 \(A_j\) 组成的数列,\(A_1\) 表示后半部分的 \(A_j\) 组成的数列(注意因为抹掉了最高位所以下标也是从 \(0\) 而不是 \(\frac{n}{2}\) 开始),那么分类之后有:

  1. \(i_0=0\Rightarrow i'=i\),有 \(j'=j\Rightarrow f(A)_ i=c(0,0)f(A_0)_ i+c(0,1)f(A_1)_ i\)
  2. \(i_0=1\Rightarrow i'=i-\frac{n}{2}\),有 \(f(A)_ {i+\frac{n}{2}}=c(1,0)f(A_0)_ i+c(1,1)f(A_1)_ i\)

当然,\(i\in[0,\frac{n}{2})\)

很明显这里问题规模减半了,内部可以分治下去求。

显然可以使用 \(\mathcal O(n)\) 的代价合并两个问题,于是做到 \(\mathcal O(n\log n)\)。分治边界处取 \(f(A)_ i=A_i\)

求出 \(f(A)\)\(f(B)\) 后进行点积得到 \(f(C)\)

接下来考虑 IFWT,把 \(f(C)\) 反演回去。

快速沃尔什逆变换 (IFWT)

对位矩阵 \(c\) 进行肉眼矩阵求逆,得到一个新的位矩阵,这个矩阵就是 IFWT 的分治系数,其他部分与 FWT 没有区别。这也在构造的时候提出了严苛的要求:矩阵必须要有逆。一个有逆的矩阵至少需要满足不存在某一行或某一列全是 \(0\)

肉眼矩阵求逆可以从 \(0\) 的位置入手,必要时可能需要列出方程。

\(f(C)\) 进行 IFWT 后得到的就是 \(C\) 的原数组了。

两条性质

FWT 本质上是线性变换,所以有 \(f(A+B)=f(A)+f(B)\)\(f(cA)=cf(A)\)(这里是点积)。

写法

首先整个东西都需要一个这样的外壳:

for(int p=2,q=1;p<=n;p<<=1,q<<=1)
    for(int i=0;i<n;i+=p) 

显然这其实就是在循环从下往上进行分治合并。每次我们只需合并 \([i,i+q)\)\([i+q,p)\) 的 FWT/IFWT 结果,合并好之后放到 \([i,p)\) 供下次使用。

讲一下或卷积。

或卷积的位矩阵是 \(\left [ \begin{matrix} 1 & 0 \\ 1 & 1\\ \end{matrix} \right ]\),注意到每一层的 \(n\) 都总是二的次幂,所以:

  • 对于前一半 \(i\in[0,\frac{n}{2})\),有 \(f(A_i)=\operatorname{merge}(c(0,0)f(A_0),c(0,1)f(A_1))\)
  • 对于后一半 \(i\in[\frac{n}{2},n)\),有 \(f(A_i)=\operatorname{merge}(c(1,0)f(A_0),c(1,1)f(A_1))\)

因为 \(c(0,1)=0\),所以我们可以直接继承前一半,然后后一半加上前一半。

也就是写成这样:

void FOR(int *A){
    for(int p=2,q=1;p<=n;p<<=1,q<<=1)
        for(int i=0;i<n;i+=p) 
            for(int j=i+q;j<=i+p-1;j++) 
                add(A[j],A[j-q]);
}

它的逆矩阵是 \(\left [ \begin{matrix} 1 & 0 \\ -1 & 1\\ \end{matrix} \right ]\),显然也是左半边不动,右一半减掉左一半,那么逆变换就是:

void IFOR(int *A){
    for(int p=2,q=1;p<=n;p<<=1,q<<=1)
        for(int i=0;i<n;i+=p) 
            for(int j=i+q;j<=i+p-1;j++) 
                des(A[j],A[j-q]);
}

于是就做完了。

实际上我们根本不用构造位矩阵,直接暴力位矩阵就行了。

posted @ 2024-01-24 09:57  Shunpower  阅读(66)  评论(0)    收藏  举报