子集卷积浅谈
简介
自己卷积解决的是一类这样的问题,就两个集合的无交并,用多项式来表示就是:
\[
f_{k}=\sum\limits_{i \& j=\varnothing,i|j=k}a_ib_j
\]
做法
我们发现,如果没有第一个限制,这个就是一个简单的 FWT 就可以解决,我们考虑一下如何转化第一个限制,实际上这个限制等同于 \(i\) 的 1 的个数加上 \(j\) 的 1 的个数等于 \(k\) 的 1 的个数。
所以我们设 \(a_{i,j}\) 表示 \(j\) 1 的个数为 \(i\) 位置上的值,只要不符合定义,就是 \(0\)。
整个式子变成了:
\[
f_{k,s}=\sum\limits_{cnt_i+cnt_j=cnt_k,i|j=s}a_{cnt_i,i}b_{cnt_j,j}
\]
注意到 FWT 的线性性,我们可以在 \(O(n^22^n)\) 内解决问题。
代码:
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 21
#define M 1100000
using namespace std;
const int INF=0x3f3f3f3f;
const int mod=1e9+9;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
int n,a[N][M],b[N][M],Cnt[M],c[N][M];
inline void FWT(int *f,int n,int op){
for(int mid=1;mid<=(n>>1);mid<<=1)
for(int l=0;l<n;l+=(mid<<1))
for(int i=l;i<=l+mid-1;i++){
if(op==0){(f[i+mid]+=f[i])%=mod;}
else{(f[i+mid]-=f[i])%=mod;}
}
}
signed main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(n);
for(int i=1;i<=(1<<20);i++){
Cnt[i]=__builtin_popcount(i);
}
for(int i=0;i<(1<<n);i++) read(a[Cnt[i]][i]);
for(int i=0;i<(1<<n);i++) read(b[Cnt[i]][i]);
n=1<<n;
for(int i=0;i<=20;i++) FWT(a[i],n,0);
// for(int i=0;i<n;i++) printf("%d ",a[0][i]);puts("");
for(int i=0;i<=20;i++) FWT(b[i],n,0);
// for(int i=0;i<n;i++) printf("%d ",b[0][i]);puts("");
for(int i=0;i<=20;i++){
for(int j=0;i+j<=20;j++){
for(int k=0;k<n;k++) c[i+j][k]=(c[i+j][k]+1ll*a[i][k]*b[j][k]%mod)%mod;
}
}
// for(int i=0;i<n;i++) printf("%d ",c[0][i]);puts("");
for(int i=0;i<=20;i++) FWT(c[i],n,1);
for(int i=0;i<n;i++) printf("%d ",(c[Cnt[i]][i]%mod+mod)%mod);
return 0;
}
例题
CF1034E
这个题是非常巧妙的想法,裸的子集卷积,但是原来的复杂度过不去,强迫我们必须利用模数为 \(4\) 的这个特点。所以我们令 \(a_i:=a_i\times 4^{f(i)}\) 其中 \(f(i)\) 是 \(i\) 在二进制下 \(1\) 的个数,这个做法之所以不能扩展的原因是这个做法不能够中途取模。
容易发现爆 ll,经过分析,发现用 ull 是正确的,如果指数比较大,就已经是 \(0\) 了,否则,容易发现没有影响。
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define int long long
#define ull unsigned long long
#define N 2100000
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
inline void FWT(ull *f,int n,int op){
for(int mid=1;mid<=(n>>1);mid<<=1)
for(int l=0;l<n;l+=(mid<<1))
for(int k=l;k<=l+mid-1;k++){
// printf("k+mid=%d k=%d\n",k+mid,k);
if(op==0) f[k+mid]+=f[k];
else f[k+mid]-=f[k];
}
}
ull a[N],b[N],c[N];
int n;
char s[N],t[N];
signed main(){
read(n);n=1<<n;
scanf("%s%s",s,t);
for(int i=0;i<n;i++) a[i]=((ull)(s[i]-'0'))<<(__builtin_popcountll(i)<<1);
for(int i=0;i<n;i++) b[i]=((ull)(t[i]-'0'))<<(__builtin_popcountll(i)<<1);
// for(int i=0;i<n;i++) printf("%d ",a[i]);puts("");
// for(int i=0;i<n;i++) printf("%d ",b[i]);puts("");
FWT(a,n,0);FWT(b,n,0);
for(int i=0;i<n;i++){
c[i]=a[i]*b[i];
// printf("i=%d a=%d b=%d c=%d\n",i,a[i],b[i],c[i]);
}
FWT(c,n,1);
for(int i=0;i<n;i++) c[i]=(c[i]/((ull)1<<(__builtin_popcountll(i)<<1)))&3;
for(int i=0;i<n;i++) printf("%llu",c[i]);
return 0;
}