快速沃尔什变换(FWT)

FWT

熟知两个序列 \(A=\{a_i\}_{i=0}^n\),\(B=\{b_i\}_{i=0}^n\) 的加法卷积为:

\[c_i=\sum\limits_{j+k=i}^na_jb_k \]

计算方式是:

\[A^\prime=FFT(A),B^\prime=FFT(B) \\ C=A^\prime\times B^\prime,IFFT(C) \]

那如果我们要计算or卷积,and卷积或是xor卷积呢?

也就是说我们要计算:

\[c_i=\sum\limits_{j\oplus k=i}^na_jb_k \]

其中,\(\oplus\) 表示一种位运算,可以解决吗?

可以,计算方式是:

\[A^\prime=FWT(A),B^\prime=FWT(B) \\ C=A^\prime\times B^\prime,IFWT(C) \]

or卷积

我们将一个多项式 \(A=a_0+a_1x+\cdots+a_nx^n\) 分成两部分:\(A_0,A_1\)

其中 \(A_0\) 就是多项式前面 \(2^{n-1}\) 项,也就是高位为 \(0\) 的部分(所以下标为 \(0\) 啊)

类似的,\(A_1\) 就是后 \(2^{n-1}\) 项

显然对于 \(A_0\) 与 \(A_1\),它们都是一个 \(2^{n-1}\) 维向量,于是我们定义 \((A,B)\) 为它们的合成,即一个 \(2^n\) 维向量:

\[(A,B)=(a_0,a_1,\cdots,a_{n},b_1,b_2,\cdots,b_n) \]

于是 \(A=(A_0,A_1)\)

类似FFT分治思想,我们先考虑最高位

对于一个最高位为 \(0\) 的指标 \(i\),显然它与最高位为 \(1\) 的指标or的时候,最高位一定为 \(1\)

对于一个最高位为 \(1\) 的指标 \(i\),它怎么or最高位都是 \(1\)

于是对于一个长度为 \(2^n\) 的 \(A\):

\[FWT(A)= \begin{cases} (FWT(A_0),FWT(A_0+A_1))&n\ge0 \\ A&n=0 \end{cases} \]

我们不妨把这个叫做分解式,下同

其中的 \(+\) 就是普通意义下的多项式加法

对于 \(FWT(A+B)\),我们有:

\[\begin{align*} FWT(A+B)&=\sum\limits_{j|k=i}^n(a_j+b_j) \\ &=\sum\limits_{j|k=i}^na_j+\sum\limits_{j|k=i}^nb_j \\ &=FWT(A)+FWT(B) \end{align*} \]

对于 \(FWT(A,B)\),依照上文定义显然有:

\[FWT(A)=(FWT(A_0),FWT(A_0+A_1))\Rightarrow FWT(A,B)=(FWT(A),FWT(A+B)) \]

现在我们要证明的就是 \(FWT(A|B)=FWT(A)\times FWT(B)\)

根据分治的特点,这里显然选用数学归纳法更加简便一些

显然 \(n=0\) 时成立,于是:

\[\begin{align*} &FWT(A|B)\\&=FWT((A|B)_0,(A|B)_1) \\ &=FWT(A_0|B_0,A_0|B_1+A_1|B_0+A_1|B_1) \\ &=(FWT(A_0|B_0),FWT(A_0|B_0+A_0|B_1+A_1|B_0+A_1|B_1)) \\ &=(FWT(A_0)\times FWT(B_0),\sum\limits_{i=0}^1\sum\limits_{j=0}^1(FWT(A_i)\times FWT(B_j))) \\ &=(FWT(A_0)\times FWT(B_0),(FWT(A_0)+FWT(A_1))\times(FWT(B_0)+FWT(B_1)) \\ &=(FWT(A_0)\times FWT(B_0),(FWT(A_0+A_1))\times(FWT(B_0+B_1)) \\ &=(FWT(A_0),FWT(A_0+A_1))\times(FWT(B_0),FWT(B_0+B_1))\tag{1} \\ &=FWT(A)\times FWT(B) \end{align*} \]

这里的 \(\times\) 似乎是对应位置相乘,因为如果做加法卷积 \((1)\) 处是不成立的

然后根据分解式,我们有:

\[IFWT(FWT(A))=A\\\Rightarrow IFWT(FWT(A_0),FWT(A_0+A_1))=A \\\Rightarrow IFWT(A)=(IFWT(A_0),IFWT(A_1-A_0)) \]

and卷积

依然沿用上文的思想与记号

如果一个指标的最高位为 \(0\),那么它怎么and都是 \(0\)

如果一个指标的最高位为 \(1\),那么它与最高位为 \(0\) 的and的时候,最高位为 \(0\),与最高位为 \(1\) 的and的时候,最高位为 \(1\)

于是:

\[FWT(A)= \begin{cases} (FWT(A_0+A_1),FWT(A_1))&n\ge0 \\ A&n=0 \end{cases} \]

类似的,对于 \(FWT(A,B)\):

\[FWT(A,B)=(FWT(A+B),FWT(B)) \]

\[\begin{align*} &FWT(A\&B)\\&=FWT((A\&B)_0,(A\&B_1)) \\ &=FWT(A_0\&B_0+A_0\&B_1+A_1\&B_0,A_1\&B_1) \\ &=(FWT(A_0\&B_0+A_0\&B_1+A_1\&B_0+A_1\&B_1),FWT(A_1\&B_1)) \\ &=(FWT(A_0+A_1),FWT(A_1))\times (FWT(B_0+B_1),FWT(B_1)) \\ &=FWT(A)\times FWT(B) \end{align*} \]

仿照or,我们有:

\[IFWT(A)=(IFWT(A_0-A_1),IFWT(A_1)) \]

xor卷积

最高位相同的xor结果为 \(0\),最高位不同的xor结果为 \(1\)

首先我们定义:

\[FWT(A)_i=\sum\limits_{\text{popcount}(i\&j)\equiv0\bmod 2}a_j-\sum\limits_{\text{popcount}(i\&j)\equiv1\bmod 2}a_j \]

其中,\(FWT(A)_i\) 表示 \(FWT(A)\) 的第 \(i\) 项系数

于是

\[FWT(A)= \begin{cases} (FWT(A_0+A_1),FWT(A_0-A_1))&n\ge0 \\ A&n=0 \end{cases} \]

于是有:

\[\begin{aligned} &FWT(A\oplus B)\\&=(FWT(A\oplus B)_0+FWT(A\oplus B)_1,FWT(A\oplus B)_0-FWT(A\oplus B)_1) \\ &=(FWT(A_0\oplus B_0+A_1\oplus B_0+A_0\oplus B_1+A_1\oplus B_1),FWT(A_0\oplus B_0+A_1\oplus B_1-A_1\oplus B_0-A_0\oplus B_1)) \\ &=((FWT(A_0)+FWT(A_1))\times(FWT(B_0)+FWT(B_1)),(FWT(A_0)-FWT(A_1))\times(FWT(B_0)-FWT(B_1)) \\ &=(FWT(A_0+A_1),FWT(A_0-A_1))\times(FWT(B_0+B_1),FWT(B_0-B_1)) \\ &=FWT(A)\times FWT(B) \end{aligned} \]

依然是根据分解式,我们有:

\[IFWT(FWT(A))=A\\\Rightarrow IFWT(FWT(A_0+A_1),FWT(A_0-A_1))=A \\\Rightarrow IFWT(A)=(\frac{IFWT(A_0+A_1)}{2},\frac{IFWT(A_0-A_1)}{2}) \]

代码实现

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define N 1000001
#define M 5001
#define INF 1100000000
#define Kafuu return
#define Chino 0
#define R register
#define C const
#define U unsigned
#define fx(l,n) inline l n
#define set(l,n,ty,len) memset(l,n,sizeof(ty)*len)
#define cpy(f,t,ty,len) memcpy(t,f,sizeof(ty)*len)
#define int long long
using namespace std;
C int mod=998244353;
fx(int,gi)(){
	R char c=getchar();R int s=0,f=1;
	while(c>'9'||c<'0'){
		if(c=='-') f=-f;
		c=getchar();
	}
	while(c<='9'&&c>='0') s=(s<<3)+(s<<1)+(c-'0'),c=getchar();
	return s*f;
}
int n,F[N],G[N],f[N],g[N],wn;
fx(void,FWTor)(int *f,short op,int x){
	R int len,hl,s,i;
	for(len=2,hl=1;len<=x;hl=len,len<<=1){
		for(s=0;s<x;s+=len){
			for(i=0;i<hl;i++){
				(f[s|i|hl]+=f[s|i]*op+mod)%=mod;
			}
		}
	}
}
fx(void,FWTand)(int *f,short op,int x){
	R int len,hl,s,i;
	for(len=2,hl=1;len<=x;hl=len,len<<=1){
		for(s=0;s<x;s+=len){
			for(i=0;i<hl;i++){
				(f[s|i]+=f[s|i|hl]*op+mod)%=mod;
			}
		}
	}
}
fx(void,FWTxor)(int *f,int op,int x){
	R int len,hl,s,i,uni;
	for(len=2,hl=1;len<=x;hl=len,len<<=1){
		for(s=0;s<x;s+=len){
			for(i=0;i<hl;i++){
				uni=f[s|i]-f[s|i|hl];
				f[s|i]=(f[s|i]+f[s|i|hl])*op%mod;
				f[s|i|hl]=(uni+mod)*op%mod;
			}
		}
	}
}
signed main(){
	n=gi();wn=1<<n;
	for(R int i=0;i<(1<<n);i++) F[i]=gi();
	for(R int i=0;i<(1<<n);i++) G[i]=gi();
	cpy(F,f,int,wn);cpy(G,g,int,wn);
	FWTor(f,1,wn);FWTor(g,1,wn);
	for(R int i=0;i<wn;i++) (f[i]*=g[i])%=mod;
	FWTor(f,-1,wn);
	for(R int i=0;i<wn;i++) printf("%lld ",f[i]);
	printf("\n");
	cpy(F,f,int,wn);cpy(G,g,int,wn);
	FWTand(f,1,wn);FWTand(g,1,wn);
	for(R int i=0;i<wn;i++) (f[i]*=g[i])%=mod;
	FWTand(f,-1,wn);
	for(R int i=0;i<wn;i++) printf("%lld ",f[i]);
	printf("\n");
	cpy(F,f,int,wn);cpy(G,g,int,wn);
	FWTxor(f,1,wn);FWTxor(g,1,wn);
	for(R int i=0;i<wn;i++) (f[i]*=g[i])%=mod;
	FWTxor(f,(mod+1)>>1,wn);
	for(R int i=0;i<wn;i++) printf("%lld ",f[i]);
	printf("\n");
}

CF449D Jzzhu and Numbers

题目分析

说实话,and背包能想出来,\(n\) 次FWT也能想出来,但是接下来怎么做确实不会,应该还是理解不透彻&不熟练...

首先这显然是个背包模型:

\[f_{i,j}=\sum\limits_{a_i\&k=j}f_{i-1,k}+f_{i-1,j} \]

观察式子,我们发现左边跟FWT正变换有亿点点相似,暴力做 \(n\) 次FWT即可

众所周知,对于and卷积,其实就是求一个指标 \(i\) 的指标超集对应的数的和

于是我们循环对每一项置 \(1\) 后FWT的结果数列中肯定没有大于二的数

我们可以根据这个性质来优化此题:

  • 如果这一项是 \(0\),也就是说不做贡献
  • 如果这一项是 \(1\),也就是说这一项翻倍

不断翻倍,也就是说贡献为 \(2\) 的幂次方倍,我们只要知道翻倍翻了几次

这简单,找超集个数即整体FWT

观察式子:

\[FWT(A)_i=\sum\limits_{j\&k=i}a_j \]

于是我们在每一位上累计 \(2\) 的幂次方倍,然后IFWT即可

//ysh SB!!!
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define N 3000001
#define M 5001
#define INF 1100000000
#define Kafuu return
#define Chino 0
#define R register
#define C const
#define U unsigned
#define fx(l,n) inline l n
#define set(l,n,ty,len) memset(l,n,sizeof(ty)*len)
#define cpy(f,t,ty,len) memcpy(t,f,sizeof(ty)*len)
#define int long long
using namespace std;
C int mod=1e9+7;
fx(int,gi)(){
	R char c=getchar();R int s=0,f=1;
	while(c>'9'||c<'0'){
		if(c=='-') f=-f;
		c=getchar();
	}
	while(c<='9'&&c>='0') s=(s<<3)+(s<<1)+(c-'0'),c=getchar();
	return s*f;
}
int n,a[N],t[N],sum[N],x=1,maxn;
bool b;
fx(int,fpow)(int a,int b){
	int sum=1;
	while(b){
		if(b&1) (sum*=a)%=mod;
		(a*=a)%=mod;
		b>>=1;
	}
	return sum;
}
fx(void,FWTand)(int *f,short op,int x){
	R int hl,len,i,s;
	for(len=2,hl=1;len<=x;hl=len,len<<=1){
		for(s=0;s<x;s+=len){
			for(i=0;i<hl;i++){
				(f[s|i]+=f[s|i|hl]*op+mod)%=mod;
			}
		}
	}
}
signed main(){
	n=gi();
	for(R int i=1;i<=n;i++) a[i]=gi(),t[a[i]]+=1,b|=a[i],maxn=max(maxn,a[i]);
	while(x<=maxn) x<<=1;
	FWTand(t,1,x);
	for(R int i=0;i<x;i++) t[i]=fpow(2,t[i])%mod;
	FWTand(t,-1,x);
	if(b) printf("%lld",t[0]);
	else printf("%lld",t[0]-1);
}
上一篇:设置mysql数据库的密码


下一篇:Gym102341【杂题】