1. 程式人生 > 其它 >子集卷積淺談

子集卷積淺談

簡介

自己卷積解決的是一類這樣的問題,就兩個集合的無交併,用多項式來表示就是:

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