位运算卷积与集合幂级数
位运算卷积:
定义位运算卷积:
第 \(i\) 项和第 \(j\) 项的乘积贡献到第 \(i⊕j\) 项。其中 \(⊕\) 是某种位运算,即:
记作:
构造 \(FWT\) 变换:
尝试把位运算卷积转化成点积。
设 \(fwt(A)\) 是幂级数 \(A\) 经过 \(FWT\) 变换之后得到的幂级数,其满足:
我们希望构造出来的是一个线性变换,设:
有:
由 \(S=A*B\) 有:
由 \(\sum_{j=0}^{n-1}\limits c(i,j)⋅S[j]=\sum_{k=0}^{n-1}\limits \sum_{p=0}^{n-1}\limits c(i,k)⋅c(i,p)⋅A[k]⋅B[p]\) 得:
因此,只需有:
另外,由于位运算每一位的独立性,有:
因此:
因此我们只要针对不同的位运算构造出一个 \(2*2\) 的矩阵即可
1. \(Or\) 卷积
设:
有:
为了方便在变换后还原,矩阵必须要有逆,因此,最终答案有两个:
对于第二个矩阵,显然有 \(c(i,j)=[j\)&\(i=j]\) 因此我们使用第二个矩阵。
还原时对矩阵求逆即可:
2. \(And\) 卷积
设:
有:
最终答案有两个:
对于第二个矩阵,显然有 \(c(i,j)=[j\)&\(i=i]\) 因此我们使用第二个矩阵。
还原时对矩阵求逆即可:
3. \(Xor\) 卷积
设:
有:
最终答案有四个:
对于第二个矩阵,显然有 \(c(i,j)=(-1)^{|i\& j|}\) 因此我们使用第二个矩阵。
还原时对矩阵求逆即可:
子集卷积
求:
若只考虑 \(i∪j=k\) ,一次 \(\text{or}\) 卷积即可,考虑 \(i∩j=∅\) 的限制,发现其等价于 \(|i|+|j|=|i\cup j|\)。
因此再开一维记录集合中的元素个数即可。
时间复杂度 \(O(n^2 2^n)\)
\(FWT\) 变换的一些性质:
\(FWT\) 是线性变换:
由定义 \(fwt(A)[i]=\sum_{j=0}^{n-1}\limits c(i,j)⋅A[j]\) 后显然可证。
- 求:
从序列 \(\{a_i\}\) 中选择一个非空子集,使子集内的数按位与起来为零
solution 1:
令 \(dp[s]\) 表示按位与起来结果为 \(s\) 的超集的方案数
\(FWT\) 求出 \(dp[s]\) ,则有:
solution 2:
令 \(dp[i][s]\) 表示到了第 \(i\) 位,运算结果位 \(s\) 的方案数,有:
对上式进行 \(fmt\) 变换,由于 \(fwt(A+B)=fwt(A)+fwt(B)\) 可知:
显然:
因此:
记 \(cnt[s]=\sum_{i=1}^{n}\limits [s=a[i]]\)
【模板】快速莫比乌斯/沃尔什变换 (FMT/FWT)
#include<bits/stdc++.h>
using namespace std;
int n;
int A[(1<<17)+5],B[(1<<17)+5];
int a[(1<<17)+5],b[(1<<17)+5],c[(1<<17)+5];
const int md=998244353;
inline void cpy(){
for(int i=0;i<(1<<n);i++)a[i]=A[i];
for(int i=0;i<(1<<n);i++)b[i]=B[i];
}
inline void solve(){
for(int i=0;i<(1<<n);i++)c[i]=(1ll*a[i]*b[i])%md;
}
inline void output(){
for(int i=0;i<(1<<n);i++)printf("%d ",(c[i]+md)%md);
puts("");
}
inline void And(int *dp,int x){
for(int i=0;i<n;i++){
for(int s=0;s<(1<<n);s++){
if((s>>i)&1)continue;
dp[s]=(dp[s]+x*dp[s|(1<<i)])%md;
}
}
}
inline void Or(int *dp,int x){
for(int i=0;i<n;i++){
for(int s=0;s<(1<<n);s++){
if((s>>i)&1)continue;
dp[s|(1<<i)]=(dp[s|(1<<i)]+x*dp[s])%md;
}
}
}
inline void Xor(int *dp,int x){
for(int i=0;i<n;i++){
for(int s=0;s<(1<<n);s++){
if((s>>i)&1)continue;
long long u=dp[s],t=dp[s|(1<<i)];
dp[s]=1ll*x*(u+t)%md;
dp[s|(1<<i)]=1ll*x*(u-t)%md;
}
}
}
int main(){
scanf("%d",&n);
for(int i=0;i<(1<<n);i++)scanf("%d",&A[i]);
for(int i=0;i<(1<<n);i++)scanf("%d",&B[i]);
cpy();Or(a,1);Or(b,1);solve();Or(c,-1);output();
cpy();And(a,1);And(b,1);solve();And(c,-1);output();
cpy();Xor(a,1);Xor(b,1);solve();Xor(c,(md+1)>>1);output();
return 0;
}
【模板】子集卷积
#include <bits/stdc++.h>
using namespace std;
int n;
int a[21][1<<20],b[21][1<<20],res[21][1<<20],cnt[1<<20];
const int md=1e9+9;
inline void fwt(int *dp,int x){
for(int i=0;i<n;i++){
for(int s=0;s<(1<<n);s++){
if((s>>i)&1)continue;
dp[s^(1<<i)]=(dp[s^(1<<i)]+x*dp[s])%md;
}
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<(1<<n);i++)cnt[i]=cnt[i>>1]+(i&1);
for(int i=0;i<(1<<n);i++)scanf("%d",&a[cnt[i]][i]);
for(int i=0;i<(1<<n);i++)scanf("%d",&b[cnt[i]][i]);
for(int i=0;i<=n;i++)fwt(a[i],1);
for(int i=0;i<=n;i++)fwt(b[i],1);
for(int i=0;i<=n;i++){
for(int j=0;j+i<=n;j++){
for(int s=0;s<(1<<n);s++){
res[i+j][s]=(res[i+j][s]+1ll*a[i][s]*b[j][s])%md;
}
}
}
for(int i=0;i<=n;i++)fwt(res[i],-1);
for(int i=0;i<(1<<n);i++)printf("%d ",(res[cnt[i]][i]+md)%md);
return 0;
}
Jzzhu and Numbers
code 1:
#include<bits/stdc++.h>
using namespace std;
int n;
int dp[1<<20],f[1<<20],cnt[1<<20];
const int md=1e9+7;
inline int pwr(int x,int y){
int res=1;
while(y){
if(y&1)res=1ll*res*x%md;
x=1ll*x*x%md;y>>=1;
}return res;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
int x;scanf("%d",&x);
dp[x]++;
}
for(int i=0;i<20;i++){
for(int s=0;s<(1<<20);s++){
if((s>>i)&1)continue;
dp[s]+=dp[s|(1<<i)];
}
}
for(int i=0;i<(1<<20);i++)f[i]=pwr(2,dp[i])-1;
long long ans=0;
for(int i=0;i<(1<<20);i++){
cnt[i]=cnt[i>>1]+(i&1);
if(cnt[i]&1)ans=(ans-f[i])%md;
else ans=(ans+f[i])%md;
}
printf("%lld",(ans+md)%md);
return 0;
}
code 2:
#include<bits/stdc++.h>
using namespace std;
int n;
int dp[1<<20],f[1<<20],cnt[1<<20];
const int md=1e9+7;
inline int pwr(int x,int y){
int res=1;
while(y){
if(y&1)res=1ll*res*x%md;
x=1ll*x*x%md;y>>=1;
}return res;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
int x;scanf("%d",&x);
dp[x]++;
}
for(int i=0;i<20;i++){
for(int s=0;s<(1<<20);s++){
if((s>>i)&1)continue;
dp[s]+=dp[s|(1<<i)];
}
}
for(int i=0;i<(1<<20);i++)dp[i]=pwr(2,dp[i]);
for(int i=0;i<20;i++){
for(int s=0;s<(1<<20);s++){
if((s>>i)&1)continue;
dp[s]=(dp[s]-dp[s|(1<<i)])%md;
}
}
printf("%d",(dp[0]+md)%md);
return 0;
}