快速沃尔什变换 FWT
思路
FWT 用以快速求:
其中 \(\oplus\) 可以是很多操作,常见的比如按位与、或、异或等。
我们考虑构造一种映射 \(\mathrm{FWT}\),满足:
和 FFT 很像。
常见实现
按位或
一种可行的构造为:
考虑如何快速变换。存在分治做法:
每次将当前序列分为左右两部分,则左侧当前最高位为 \(0\),右侧当前最高位为 \(1\),先递归处理两部分,然后将左侧的答案加到右侧对应位置即可。当前最高位是关于下标的,可以感性理解,理性理解的话就是一侧的块长关于 \(2\) 的对数那一位。
记分出来的两半序列为 \(f_0,f_1\),\(\mathrm{merge}\) 表示拼接两个序列,那么可以写作:
逆变换的话减掉就行了:
通常使用非递归实现。
按位与
和按位或很像,一种可行的构造为:
剩下的也基本一样,也是分治:
逆变换:
按位异或
记 \(x\circ y=\mathrm{popcnt}(x\&y)\bmod 2\),那么有 \((x\circ y)\oplus(x\circ z)=x\circ(y\oplus z)\)。
存在构造:
存在分治做法:
逆变换:
值得注意的是,逆变换的时候还有一种写法。可以在过程中先不除 \(2\),变换结束后再给每个数统一除 \(2^n\) 即可,这是因为每个数都恰好少除了 \(n\) 次。不过这么写要注意数据范围。
三份代码
注意逆变换时 orFWT 和 andFWT \(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|}\)。
那么答案就是:
后面那个是一个异或卷积形式,直接 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\) 为 \(val(S\oplus T)\)。由于 \(S=T\oplus S\oplus T\),于是一个列状态的答案即为:
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\) 的逆元。有:
我们考虑异或卷积最后统一除以 \(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]);
}

浙公网安备 33010602011771号