Loading

子集卷积浅谈

简介

自己卷积解决的是一类这样的问题,就两个集合的无交并,用多项式来表示就是:

\[ 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;
}

posted @ 2022-02-20 14:40  hyl天梦  阅读(70)  评论(0编辑  收藏  举报