快速沃尔什变换 FWT

思路

FWT 用以快速求:

\[h(x)\sum_{a\oplus b=x}f(a)g(b) \]

其中 \(\oplus\) 可以是很多操作,常见的比如按位与、或、异或等。

我们考虑构造一种映射 \(\mathrm{FWT}\),满足:

\[\mathrm{FWT}(h)=\mathrm{FWT}(f)\cdot\mathrm{FWT}(g) \]

和 FFT 很像。

常见实现

按位或

一种可行的构造为:

\[\mathrm{FWT}(f)(i)=\sum_{j|i=i}f(j) \]

考虑如何快速变换。存在分治做法:

每次将当前序列分为左右两部分,则左侧当前最高位为 \(0\),右侧当前最高位为 \(1\),先递归处理两部分,然后将左侧的答案加到右侧对应位置即可。当前最高位是关于下标的,可以感性理解,理性理解的话就是一侧的块长关于 \(2\) 的对数那一位。

记分出来的两半序列为 \(f_0,f_1\)\(\mathrm{merge}\) 表示拼接两个序列,那么可以写作:

\[\mathrm{FWT}(f)=\mathrm{merge}(f_0,f_1+f_0) \]

逆变换的话减掉就行了:

\[\mathrm{IFWT}(f)=\mathrm{merge}(f_0,f_1-f_0) \]

通常使用非递归实现。

按位与

和按位或很像,一种可行的构造为:

\[\mathrm{FWT}(f)(i)=\sum_{j\&i=i}f(j) \]

剩下的也基本一样,也是分治:

\[\mathrm{FWT}(f)=\mathrm{merge}(f_0+f_1,f_1) \]

逆变换:

\[\mathrm{IFWT}(f)=\mathrm{merge}(f_0-f_1,f_1) \]

按位异或

\(x\circ y=\mathrm{popcnt}(x\&y)\bmod 2\),那么有 \((x\circ y)\oplus(x\circ z)=x\circ(y\oplus z)\)

存在构造:

\[\mathrm{FWT}(f)(i)=\sum_{j\circ i=0}f(j)-\sum_{j\circ i=1}f(j) \]

存在分治做法:

\[\mathrm{FWT}(f)=\mathrm{merge}(f_0+f_1,f_0-f_1) \]

逆变换:

\[\mathrm{IFWT}(f)=\mathrm{merge}(\frac{f_0+f_1}{2},\frac{f_0-f_1}{2}) \]

值得注意的是,逆变换的时候还有一种写法。可以在过程中先不除 \(2\),变换结束后再给每个数统一除 \(2^n\) 即可,这是因为每个数都恰好少除了 \(n\) 次。不过这么写要注意数据范围。

三份代码

注意逆变换时 orFWTandFWT \(x\) 带入 \(-1\)xorFWT 带入 \(\frac{1}{2}\)

inline vec<mint> orFWT(vec<mint> v,mint x=1){
	int n=v.size();
	for(int st=2,k=1;st<=n;st<<=1,k<<=1)
		for(int i=0;i<n;i+=st)repl(j,0,k)v[i+j+k]+=v[i+j]*x;
	return v;
}
inline vec<mint> andFWT(vec<mint> v,mint x=1){
	int n=v.size();
	for(int st=2,k=1;st<=n;st<<=1,k<<=1)
		for(int i=0;i<n;i+=st)repl(j,0,k)v[i+j]+=v[i+j+k]*x;
	return v;
}
inline vec<mint> xorFWT(vec<mint> v,mint x=1){
	int n=v.size();
	for(int st=2,k=1;st<=n;st<<=1,k<<=1)
		for(int i=0;i<n;i+=st)repl(j,0,k)
			v[i+j]+=v[i+j+k],v[i+j+k]=v[i+j]-v[i+j+k]-v[i+j+k],
			v[i+j]*=x,v[i+j+k]*=x;
	return v;
}

习题

CF850E Random Elections

首先三个人本质等价,因此只考虑一个人的概率即可,并且只有两场选举与之有关。

考虑一个选民的投票情况对应其偏好情况,不难发现若两次结果相同对应两种,不同则只对应一种。那么把每个选民的票整合起来,两场情况分别记作 \(A,B\),对应方案数为 \(2^{n-|A\oplus B|}\)

那么答案就是:

\[\sum_x 2^{n-|x|}\sum_{i\oplus j=x}f_if_j \]

后面那个是一个异或卷积形式,直接 FWT 即可。

code
const int N=20;

int n;
vec<mint> f;

inline vec<mint> FWT(vec<mint> v,mint x=1){
	int n=v.size();
	for(int st=2,k=1;st<=n;st<<=1,k<<=1)
		for(int i=0;i<n;i+=st)repl(j,0,k)
			v[i+j]+=v[i+j+k],v[i+j+k]=v[i+j]-v[i+j+k]-v[i+j+k],
			v[i+j]*=x,v[i+j+k]*=x;
	return v;
}

inline void Main(){
	cin>>n;f.resize(1<<n);
	repl(i,0,1<<n){
		char ch;cin>>ch;
		f[i]=ch-'0';
	}
	f=FWT(f);repl(i,0,1<<n)f[i]*=f[i];f=FWT(f,(mint)1/2);
	mint ans;repl(s,0,1<<n)ans+=f[s]*qpow(2,n-__builtin_popcount(s));
	put(ans*3);
}

CF662C Binary Table

不妨先考虑一个暴力,枚举行的状态,然后独立考虑每列状态求答案即可。

考虑优化,我们从“列状态”入手,记这个列状态 \(S\) 出现了 \(cnt_S\) 次,则答案即为:

\[\min_S \sum_T cnt_T\min(|S\oplus T|,n-|S\oplus T|) \]

记后面一坨 \(\min\)\(val(S\oplus T)\)。由于 \(S=T\oplus S\oplus T\),于是一个列状态的答案即为:

\[f(S)=\sum_{x\oplus y=S}cnt_xval_y \]

FWT 即可。

code
const int N=20,M=1e5+5;

int n,m;
int c[N][M];
vec<ll> a,b;

inline vec<ll> FWT(vec<ll> v,ll x=1){
	int n=v.size();
	for(int st=2,k=1;st<=n;st<<=1,k<<=1)
		for(int i=0;i<n;i+=st)repl(j,0,k)
			v[i+j]+=v[i+j+k],v[i+j+k]=v[i+j]-v[i+j+k]-v[i+j+k],
			v[i+j]/=(x?1:2),v[i+j+k]/=(x?1:2);
	return v;
}

inline void Main(){
	cin>>n>>m;
	a.resize(1<<n),b.resize(1<<n);
	repl(i,0,1<<n)b[i]=min(__builtin_popcount(i),n-__builtin_popcount(i));
	repl(i,0,n)rep(j,1,m)
		{char ch;cin>>ch;c[i][j]=ch-'0';}
	rep(j,1,m){
		int st=0;
		repl(i,0,n)st|=c[i][j]<<i;
		++a[st];
	}
	a=FWT(a),b=FWT(b);repl(i,0,1<<n)a[i]*=b[i];a=FWT(a,0);
	ll ans=INF;
	repl(i,0,1<<n)chmin(ans,a[i]);
	put(ans);
}

CF453D Little Pony and Elements of Harmony

不难写成异或卷积形式。考虑 \(t\) 次如何解决,可以 FWT 后先乘上 \(t\) 次再 IFWT,显然是正确的,快速幂即可。

但是是任意模数,不一定有 \(2\) 的逆元。有:

\[a\equiv b\pmod p\Leftrightarrow \frac{a}{d}\equiv\frac{b}{d}\pmod {\frac{p}{d}},d|\gcd\left\{a,b,p\right\} \]

我们考虑异或卷积最后统一除以 \(2^n\) 的写法,给模数先乘上 \(2^n\),IFWT 完统一除即可。得开 __int128

code
const int N=20;

int n;
ll t,p,b[N+1];
vec<i128> e,c;

inline vec<i128> FWT(vec<i128> v,int x=1){
	int n=v.size();
	for(int st=2,k=1;st<=n;st<<=1,k<<=1)
		for(int i=0;i<n;i+=st)repl(j,0,k)
			v[i+j]+=v[i+j+k],v[i+j]%=p,v[i+j+k]=(v[i+j]+p-v[i+j+k]+p-v[i+j+k])%p;
	if(!x)repl(i,0,n)v[i]/=n;
	return v;
}

inline void Main(){
	cin>>n>>t>>p;p*=1<<n;mod=p;
	e.resize(1<<n),c.resize(1<<n);
	repl(i,0,1<<n){
		int v;cin>>v;
		e[i]=v;
	}
	rep(i,0,n)cin>>b[i];
	repl(i,0,1<<n)c[i]=b[__builtin_popcount(i)];
	e=FWT(e),c=FWT(c);
	repl(i,0,1<<n)e[i]=e[i]*qpow(c[i],t)%p;
	e=FWT(e,0);
	repl(i,0,1<<n)put((ll)e[i]);
}
posted @ 2025-12-21 14:01  LastKismet  阅读(5)  评论(0)    收藏  举报