位运算卷积学习笔记

位运算卷积学习笔记

位运算卷积,即快速沃尔什变换 \(\text{FWT}\) 和快速莫比乌斯变换 \(\text{FMT}\),但事实上最常用的是 \(\text{FWT}\),因为 \(\text{FMT}\) 所求解的内容是 \(\text{FWT}\) 的子集。

位运算卷积

首先要知道位运算卷积指的是

\[c_i=\sum_{j\odot k=i}a_jb_k \]

形式的式子,其中 \(\odot\) 代指了任意位运算符,也就是 \(\text{or},\text{and},\text{xor}\)。利用 \(\text{FMT}\) 能够求解 \(\text{or},\text{and}\) 卷积,而利用 \(\text{FWT}\) 能够求解 \(\text{or},\text{and},\text{xor}\) 卷积。

快速莫比乌斯变换

\(\text{or}\) 卷积

先考虑暴力求解的时间复杂度是 \(O(n^2)\) 的,不妨考虑和 \(\text{FFT}\) 相同的思路,找到一个 \(\text{FMT}(a)\) 满足 \(\text{FMT}(c)_i=\text{FMT}(a)_i\times\text{FMT}(b)_i\),这样我们便可以通过顺逆变换快速求出 \(c\)

定义 \(\text{FMT}(a)_i=\sum_{j\cup i=i}a_j\),其中 \(\cup\) 表示按位或,而这种构造显然符合要求,因为有

\[\begin{aligned}\text{FMT}(a)_i\times\text{FMT}(b)_i&=\sum_{j\cup i=i}a_j\sum_{k\cup i=i}b_k\\&=\sum_{(j\cup k)\cup i=i}a_jb_k\\&=\text{FMT}(c)_i \end{aligned} \]

由此我们得到了一个构造,而且不难发现这其实就是 \(a\) 的高维前缀和,按照前缀和的思路将 \(n\) 维二进制看作 \(n\) 维数组求解前缀和即可。而对于逆变换只需要进行差分即可。时间复杂度是 \(O(n\log n)\) 的。

代码

int n;
int A[1<<n],B[1<<n],C[1<<n];

void FMT_OR(int n,int *a,int type){
	for(int i=0;i<n;i++){
		for(int j=0;j<(1<<n);j++){
			if(j&(1<<i))a[j]+=type*a[j^(1<<i)];
		}
	}
	return ;
}

int main(){
	cin>>n;
	for(int i=0;i<(1<<n);i++)cin>>A[i];
	for(int i=0;i<(1<<n);i++)cin>>B[i];
	FMT_OR(n,A,1);
	FMT_OR(n,B,1);
	for(int i=0;i<(1<<n);i++)C[i]=A[i]*B[i];
	FMT_OR(n,C,-1);
	for(int i=0;i<(1<<n);i++)cout<<C[i]<<" ";
	return 0;
}

\(\text{and}\) 卷积

\(\text{or}\) 卷积同理,考虑构造一个 \(\text{FMT}(a)_i\),在这里定义 \(\text{FMT}(a)_i=\sum_{j\cap i=i}a_j\)\(\cap\) 表示按位与,那么有

\[\begin{aligned}\text{FMT}(a)_i\times \text{FMT}(b)_i&=\sum_{j\cap i=i}a_j\sum_{k\cap i=i}b_k\\&=\sum_{(j\cap k)\cap i=i}a_jb_k\\&=\text{FMT}(c)_i \end{aligned} \]

于是考虑如何求解 \(\text{FMT}(a)_i\),发现其为高位后缀和的形式,于是有复杂度 \(O(n\log n)\)

代码

int n;
int A[1<<n],B[1<<n],C[1<<n];

void FMT_AND(int n,int *a,int type){
	for(int i=0;i<n;i++){
		for(int j=0;j<(1<<n);j++){
			if(j&(1<<i))a[j^(1<<i)]+=type*a[j];
		}
	}
	return ;
}

int main(){
	cin>>n;
	for(int i=0;i<(1<<n);i++)cin>>A[i];
	for(int i=0;i<(1<<n);i++)cin>>B[i];
	FMT_AND(n,A,1);
	FMT_AND(n,B,1);
	for(int i=0;i<(1<<n);i++)C[i]=A[i]*B[i];
	FMT_AND(n,C,-1);
	for(int i=0;i<(1<<n);i++)cout<<C[i]<<" ";
	return 0;
}

快速沃尔什变换

\(\text{or}\) 变换

其实有关于 \(\text{or},\text{and}\)\(\text{FWT}\)\(\text{FMT}\) 的构造完全相同,只是求解的方法不同而已,我们在这里考虑分治的做法,也就是更接近 \(\text{FFT}\) 的做法。依旧构造 \(\text{FWT}(a)_i=\sum_{j\cup i=i}a_j\),正确性不加复述,下面考虑分治。对于分治序列 \(\text{FWT}(a)\) 的第 \(i\) 位,我们将其分为两部分 \(\text{FWT}(a)_0,\text{FWT}(a)_1\),分别表示在 \(i-1\) 位分治后序列左半部分和右半部分,于是有

\[\text{FWT}(a)=\text{merge}(\text{FWT}(a)_0,\text{FWT}(a)_0+\text{FWT}(a)_1) \]

这个正确性是显然的,因为对于 \(\text{FWT}(a)_0\) 来说,其分治位置上均为 \(0\),所需要累加的对应位置 \(j\) 若要满足 \(j\cup i=i\) 则其当前分治位也需为 \(0\)。而对于 \(\text{FWT}(a)_1\) 来说,\(j\) 的当前分治位即可以为 \(1\),也可以为 \(0\),因此只需要将后半段加上前半段即可。

关于逆变换则恰相反,只需要将前半段对后半段的贡献消去即可,也就是在后半段减去前半段。也就是

\[a=\text{merge}(a_0,a_1-a_0) \]

代码

int n;
int A[1<<n],B[1<<n],C[1<<n];

void FWT_OR(int n,int *a,int type){
	for(int len=1;len<n;len<<=1){
		for(int l=0;l<n;l+=(len<<1)){
			for(int k=0;k<len;k++)a[l+len+k]+=type*a[l+k];
		}
	}
	return ;
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n;
	for(int i=0;i<(1<<n);i++)cin>>A[i];
	for(int i=0;i<(1<<n);i++)cin>>B[i];
	FWT_OR(1<<n,A,1);
	FWT_OR(1<<n,B,1);
	for(int i=0;i<(1<<n);i++)C[i]=A[i]*B[i];
	FWT_OR(1<<n,C,-1);
	for(int i=0;i<(1<<n);i++)cout<<C[i]<<" ";
	return 0;
}

\(\text{and}\) 卷积

依旧同理有 \(\text{FWT}(a)_i=\sum_{j\cap i=i}a_j\),正确性不加复述,考虑分治的做法。对于分治序列 \(\text{FWT}(a)\) 的第 \(i\) 位,我们将其分为两部分 \(\text{FWT}(a)_0,\text{FWT}(a)_1\),分别表示在 \(i-1\) 位分治后序列左半部分和右半部分,于是有

\[\text{FWT}(a)=\text{merge}(\text{FWT}(a)_0+\text{FWT}(a)_1,\text{FWT}(a)_1) \]

同理,当第 \(i\) 位为 \(0\) 时,\(j\) 的当前分治位即可以是 \(0\) 也可以是 \(1\),而当第 \(i\) 位为 \(1\) 时,\(j\) 的当前分治位只能是 \(1\),逆变换同理减回去即可。

\[a=\text{merge}(a_0-a_1,a_1) \]

代码

int n;
int A[1<<n],B[1<<n],C[1<<n];

void FWT_AND(int n,int *a,int type){
	for(int len=1;len<n;len<<=1){
		for(int l=0;l<n;l+=(len<<1)){
			for(int k=0;k<len;k++)a[l+k]+=type*a[l+len+k];
		}
	}
	return ;
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n;
	for(int i=0;i<(1<<n);i++)cin>>A[i];
	for(int i=0;i<(1<<n);i++)cin>>B[i];
	FWT_AND(1<<n,A,1);
	FWT_AND(1<<n,B,1);
	for(int i=0;i<(1<<n);i++)C[i]=A[i]*B[i];
	FWT_AND(1<<n,C,-1);
	for(int i=0;i<(1<<n);i++)cout<<C[i]<<" ";
	return 0;
}

\(\text{xor}\) 卷积

引入一个新的运算符 \(\circ\)。定义 \(x\circ y=\text{popcnt}(x\cap y)\bmod 2\),其中 \(\text{popcnt}(x)\) 表示 \(x\) 二进制下 \(1\) 的个数。我们发现它满足 \((x\circ y)\oplus(x\circ z)=x\circ (y\oplus z)\)

更感性的理解,\(x\circ y\) 表示 \(x,y\) 均为 \(1\) 位数的奇偶性。对于每一位的 \(x,y,z\),可以列出下表

\(x\) \(y\) \(z\) \(y\oplus z\) \(x\circ y\) \(x\circ z\) \(x\circ (y\oplus z)\)
\(0\) \(0\) \(0\) \(0\) \(0\) \(0\) \(0\)
\(0\) \(0\) \(1\) \(1\) \(0\) \(0\) \(0\)
\(0\) \(1\) \(0\) \(1\) \(0\) \(0\) \(0\)
\(0\) \(1\) \(1\) \(0\) \(0\) \(0\) \(0\)
\(1\) \(0\) \(0\) \(0\) \(0\) \(0\) \(0\)
\(1\) \(0\) \(1\) \(1\) \(0\) \(1\) \(1\)
\(1\) \(1\) \(0\) \(1\) \(1\) \(0\) \(1\)
\(1\) \(1\) \(1\) \(0\) \(1\) \(1\) \(0\)

不难看出,不管 \(x,y,z\) 的每一位如何变化,对应位对答案贡献的奇偶性始终不变,故上式成立。

由此,设 \(\text{FWT}(a)_i=\sum_{j\circ i=0}a_j-\sum_{j\circ i=1}a_j\)。则有

\[\begin{aligned}\text{FWT}(a)_i\times\text{FWT}(b)_i&=(\sum_{j\circ i=0}a_j-\sum_{j\circ i=1}a_j)(\sum_{j\circ i=0}b_j-\sum_{j\circ i=1}b_j)\\&=(\sum_{j\circ i=0}a_j\sum_{k\circ i=0}b_k+\sum_{j\circ i=1}a_j\sum_{k\circ i=1}b_k)-(\sum_{j\circ i=0}a_j\sum_{k\circ i=1}b_k+\sum_{j\circ i=1}a_j\sum_{k\circ i=0}b_k)\\&=\sum_{(j\oplus k)\circ i=0}a_jb_k-\sum_{(j\oplus k)\circ i=1}a_jb_k\\&=\text{FWT}(c)_i \end{aligned} \]

由此,该构造的正确性得证,接着考虑如何分治求解。当前位 \(i\)\(0\) 时,不论 \(j\)\(0,1\)\(i\circ j\) 均为 \(0\),那么 \(\text{FWT}(a)_0,\text{FWT}(a)_1\) 对该位的贡献均为正,而当 \(i\)\(1\) 时,如果 \(j\)\(0\),则对答案没有影响,但若取 \(1\),则应对原贡献取负。即

\[\text{FWT}(a)=\text{merge}(\text{FWT}(a)_0+\text{FWT}(a)_1,\text{FWT}(a)_0-\text{FWT}(a)_1) \]

而在逆变换时有

\[a=\text{merge}(\frac{a_0+a_1}{2},\frac{a_0-a_1}{2}) \]

于是对于传参稍作改动,逆变换时 \(\text{type}\)\(\frac{1}{2}\)

代码

int n;
int A[1<<n],B[1<<n],C[1<<n];

void FWT_XOR(int n,int *a,int type){
	for(int len=1;len<n;len<<=1){
		for(int l=0;l<n;l+=(len<<1)){
			for(int k=0;k<len;k++){
				ll A=a[l+k],B=a[l+len+k];
				a[l+k]=(A+B)/type;
				a[l+len+k]=(A-B)/type;
			}
		}
	}
	return ;
}

int main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n;
	for(int i=0;i<(1<<n);i++)cin>>A[i];
	for(int i=0;i<(1<<n);i++)cin>>B[i];
	FWT_XOR(1<<n,A,1);
	FWT_XOR(1<<n,B,1);
	for(int i=0;i<(1<<n);i++)C[i]=A[i]*B[i];
	FWT_XOR(1<<n,C,2);
	for(int i=0;i<(1<<n);i++)cout<<C[i]<<" ";
	return 0;
}
posted @ 2024-07-30 19:33  DycIsMyName  阅读(15)  评论(0编辑  收藏  举报