FWT和子集卷积
FWT
用于解决位运算卷积,形如对\(i=0,1,2,\cdots n\),求出\(c_i=\sum\limits_{j\circ k=i}a_jb_k\),其中\(\circ\)是一种位运算。
经常使用的\(\circ\)是按位或,按位与和异或。
以下\(n\)为二进制位数,数组长度为\(2^n\)。
类比FFT,我们想要设计两种变换,DWT和IDWT,使之满足如下性质:
-
可以在\(O(n2^n)\)内完成。
-
\(\text{DWT}(\{a\})_i\cdot\text{DWT}(\{b\})_i=\text{DWT}(\{c\})_i\)。
以下记\(A_i=\text{DWT}(\{a\})_i\)。
可以通过构造得到\(A_i\)的表达式,再通过分治计算,可以参考oiwiki。
这里参考cmd的博客,给出另一种理解FWT的方式。
不妨设\(c(i,j)\)表示\(a_j\)对\(A_i\)的贡献系数,那么可以知道:
由\(A_i\cdot B_i=C_i\),一通化简可以得到\(c(i,j)c(i,k)=c(i,j\circ k)\),其中\(\circ\)是一种位运算,与卷积中的位运算相同。
并且由于考虑贡献系数时关注着二进制的每一位,于是\(c(i,j)\)其实是二者对应二进制位的贡献系数的乘积。
所以只需\(c(0/1,0/1)\)便可描述所有贡献系数。
直接用式子算肯定不行,考虑折半:
于是可以类似FFT进行迭代。
这个\(c\)被称为位矩阵。
IDWT就是对\(c\)求个逆就好,过程不变。
以下来推\(c\),所有原理都是\(c(i,j)c(i,k)=c(i,j\circ k)\)。不需要通过意义。
由于这里构造的方程是二次的,一般都有多解。我们选取的解必须保证\(c\)有逆。
\(\text{or}\)
经过计算,\(c\)有两解,\(\begin{bmatrix}1 & 1 \\1 & 0\end{bmatrix}\)或\(\begin{bmatrix}1 & 0\\1 & 1\end{bmatrix}\)。
我们一般选取\(c=\begin{bmatrix}1 & 0\\1 & 1\end{bmatrix}\),因为这个同时兼顾了一些意义,\(c(i,j)=[i \& j=j]\),相当于子集求和。
求个逆,\(c^{-1}=\begin{bmatrix}1 & 0\\-1 & 1\end{bmatrix}\)。
\(\text{and}\)
经过计算,\(c\)有两解,\(\begin{bmatrix}0 & 1 \\1 & 1\end{bmatrix}\)或\(\begin{bmatrix}1 & 1\\0 & 1\end{bmatrix}\)。
一般采用第二种,\(c=\begin{bmatrix}1 & 1\\0 & 1\end{bmatrix}\)。这里 \(c(i,j)=[i\& j=i]\),相当于超集求和。
求个逆,\(c^{-1}=\begin{bmatrix}1 & -1\\0 & 1\end{bmatrix}\)。
\(\text{xor}\)
经计算,\(c\)有两解,\(\begin{bmatrix}1 & 1 \\-1 & 1\end{bmatrix}\)或\(\begin{bmatrix}1 & 1\\1 & -1\end{bmatrix}\)。
一般采用第二种,\(c=\begin{bmatrix}1 & 1\\1 & -1\end{bmatrix}\)。这也兼顾了一些意义,\(c(i,j)=(-1)^{|i\& j|}\)。
求个逆,\(c^{-1}=\begin{bmatrix}0.5 & 0.5\\0.5 & -0.5\end{bmatrix}\)
板子
#include<bits/stdc++.h>
using namespace std;
#define gc getchar
#define pc putchar
int rd(){
int f=1,r=0;
char ch=gc();
while(!isdigit(ch)){ if(ch=='-') f=-1;ch=gc();}
while(isdigit(ch)){ r=(r<<1)+(r<<3)+(ch^48);ch=gc();}
return f*r;
}
void wt(int x){
static int stk[30],tp=0;
if(x<0) x=-x,pc('-');
do{
stk[++tp]=x%10,x/=10;
}while(x);
while(tp) pc(stk[tp--]+'0');
}
const int mo=998244353,inv2=(mo+1)/2;
const int cor[2][2]={{1,0},{1,1}},icor[2][2]={{1,0},{mo-1,1}};
const int cand[2][2]={{1,1},{0,1}},icand[2][2]={{1,mo-1},{0,1}};
const int cxor[2][2]={{1,1},{1,mo-1}},icxor[2][2]={{inv2,inv2},{inv2,mo-inv2}};
const int maxn=270000;
int n,a[maxn],b[maxn],c[maxn];
void fwt(int f[],int n,const int d[2][2]){
for(int h=2;h<=(1<<n);h<<=1){
for(int i=0;i<(1<<n);i+=h){
for(int k=i;k<i+h/2;++k){
int t=f[k];
f[k]=(1ll*d[0][0]*f[k]%mo+1ll*d[0][1]*f[k+h/2]%mo)%mo;
f[k+h/2]=(1ll*d[1][0]*t%mo+1ll*d[1][1]*f[k+h/2]%mo)%mo;
}
}
}
}
void calc(const int d1[2][2],const int d2[2][2]){
fwt(a,n,d1),fwt(b,n,d1);
for(int i=0;i<(1<<n);++i) c[i]=1ll*a[i]*b[i]%mo;
fwt(c,n,d2),fwt(a,n,d2),fwt(b,n,d2);
for(int i=0;i<(1<<n);++i) wt(c[i]),pc(' ');
pc('\n');
}
int main(){
n=rd();
for(int i=0;i<(1<<n);++i) a[i]=rd();
for(int i=0;i<(1<<n);++i) b[i]=rd();
calc(cor,icor);
calc(cand,icand);
calc(cxor,icxor);
return 0;
}
FWT 是线性变换
-
\(\text{DWT}(A+B)=\text{DWT}(A)+\text{DWT}(B)\)
-
\(\text{DWT}(kA)=k\text{DWT}(A)\)
这意味着我们可以先对 \(A\) 进行 DWT 后做运算,最后再 IDWT 回来。
\(K\)进制下的FWT
子集卷积
对于任意\(U\)满足\(0\le |U|< 2^n\),要求:
后面一坨就是无交并。这里有组合意义。
首先转化一下,\([L\cup R=U][L\cap R=\emptyset]=[|L|+|R|=|U|][L\cup R=U]\)。
\([L\cup R=U]\)像按位与卷积,\([|L|+|R|=|U|]\)像加法卷积,于是考虑让他们分开卷。
将\(2^U\)拆成几个等价类,等价关系是集合大小相等。设\(f'_{i,L}=f_L[|L|=i]\),\(g\)同理。
于是\(h=\sum\limits_{i+j=n}f'_i\times g'_j\),这里\(\times\)是按位与卷积。
这样就可以求出\(h\),知道了每一个\(U\)对应的\(h_U\)。
\(O(n^22^n)\)。
让我们更深入一些,学习一下集合幂级数。

浙公网安备 33010602011771号