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,使之满足如下性质:

  1. 可以在\(O(n2^n)\)内完成。

  2. \(\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=\sum\limits_jc(i,j)a_j \]

\(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)\)便可描述所有贡献系数。

直接用式子算肯定不行,考虑折半:

\[A_i=c(i_0,0)\sum\limits_{0\le j<2^{n-1}}c(i',j')a_j+c(i_0,1)\sum\limits_{2^{n-1}\le j<2^n}c(i',j')a_j \]

于是可以类似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\),要求:

\[h_U=\sum\limits_{L\subseteq U}\sum\limits_{R\subseteq U}f_{L}g_{R}[L\cup R=U][L\cap R=\emptyset] \]

后面一坨就是无交并。这里有组合意义。

首先转化一下,\([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)\)

让我们更深入一些,学习一下集合幂级数。

posted @ 2025-03-26 10:06  RandomShuffle  阅读(49)  评论(0)    收藏  举报