芝士:FWT

背景

解决\(\begin{cases}h_k=\sum_{i|j=k}g_if_j\\h_k=\sum_{i\oplus j=k}g_if_j\\h_k=\sum_{i\&j=k}g_if_j\\\end{cases}\)的问题

思路

或运算

过程

我们先定义函数\(FWT(A)_x=\sum_{j|x=x}a_j\)

别管这个函数怎么来的

然后我们尝试将两个这样的函数相乘

\[\begin{aligned}FWT(F)_x*FWT(G)_x&=(\sum_{i|x=x}f_i)(\sum_{i|x=x}{g_i})\\&=\sum_{i|x=x,j|x=x}f_ig_j\\&=\sum_{(i|j)|x=x}f_ig_j\\&=\sum_{k|x=x}h_i\\&=FWT(H)_x\end{aligned} \]

那么既有\(FWT(H)_x=FWT(F)_xFWT(G)_x\)

那么问题就转换成为了怎么快速得到\(FWT(H)_x\)的值,以及怎么通过\(FWT(H)_x\)转换回\(H\)函数

观察一下\(FWT(F)_x=\sum_{i|x=x}f_i\),这里的\(i\)具体一点,就是\(x\)进行二进制分解之后的子集

如果要直接处理出\(FWT(F)_i,i\in[0,n-1]\),思考这\(FWT(F)_i\)之间是否会有一点联系?

先人为地将\(n\)强行调整成为\(n=2^k\),设\(F_0\)\(F\)的前半段,\(F_1\)\(F\)的后半段,

那么有\(FWT(F)=merge(FWT(F_0),FWT(F_0)+FWT(F_1))\)

这里下标是指函数的返回为一个序列,传进去也是一个序列,\(merge\)函数将两个序列接起来,\(+\)为对位相加

这个式子应该很好理解吧,

首先明确一点,一个位置只会向比他大的位置产生贡献,那么前半段就自然是一样的,接着考虑后半段,因为区间一定是\(2^i\),故下标的关系一定是\(x-(1<<i)=y\)的形式,就是相当于分成前半段和后半段算这个东西的贡献,

边界情况就是只有一个数

时间复杂度即为\(O(nlog_n)\)

接着考虑怎么从\(FWT(F)\)改成到\(F\),这不就相当于\(FWT\)的逆操作?

\(FWT\)的过程反过来看即可

代码

void fwtor(long long *f,int l=0,int r=n-1)
{
    if(r==l)
        return;
    int mid=(r+l)>>1,len=(r-l+1)>>1;
    fwtor(f,l,mid);fwtor(f,mid+1,r);
    for(int i=mid+1;i<=r;i++)
        f[i]+=f[i-len];
}
void idfwtor(long long *f,int l=0,int r=n-1)
{
    if(r==l)   
        return;
    int mid=(r+l)>>1,len=(r-l+1)>>1;
    for(int i=mid+1;i<=r;i++)
        f[i]-=f[i-len];
    idfwtor(f,l,mid);idfwtor(f,mid+1,r);
}

并运算

思路

按照或运算的思路 ,证明的过程相似

\(FWT(F)_x=\sum_{i\&x=x}f_i\)

那么有\(FWT(H)_x=FWT(F)_xFWT(G)_x\)

然后\(FWT(F)_x=merge(FWT(F_0)+FWT(F_1),FWT(F_1))\)

代码

void fwtand(long long *f,int l=0,int r=n-1)
{
    if(r==l)
        return;
    int mid=(r+l)>>1,len=(r-l+1)>>1;
    fwtand(f,l,mid);fwtand(f,mid+1,r);
    for(int i=l;i<=mid;i++)
        f[i]=f[i]+f[i+len];
}
void idfwtand(long long *f,int l=0,int r=n-1)
{
    if(r==l)   
        return;
    int mid=(r+l)>>1,len=(r-l+1)>>1;
    for(int i=l;i<=mid;i++)
        f[i]=f[i]-f[i+len];
    idfwtand(f,l,mid);idfwtand(f,mid+1,r);
}

异或

思路

设函数\(cnt(x)\)表示\(x\)二进制分解下\(1\)的个数

定义运算\(x\oplus y=cnt(x\&y)\%2\)

那么很显然有一个性质\((a\oplus x) xor ( a\oplus y) =a\oplus(x\space xor\space y)\)

假设\(FWT(F)_x=\sum_{i\oplus x=0}f_i-\sum_{j\oplus x=1}{f_j}\)

\[\begin{aligned}FWT(F)_xFWT(G)_x&=(\sum_{i\oplus x=0}f_i-\sum_{i\oplus x=1}f_i)(\sum_{i\oplus x=0}g_i-\sum_{i\oplus x=1}g_i)\\&=(\sum_{i\oplus x=0}f_i)(\sum_{i\oplus x=0}g_i)-(\sum_{i\oplus x=0}f_i)(\sum_{i\oplus x=1}g_i)-(\sum_{i\oplus x=1}f_i)(\sum_{i\oplus x=0}g_i)+(\sum_{i\oplus x=1}f_i)(\sum_{i\oplus x=1}g_i)\\&=\sum_{x\oplus (i\space xor\space j) =0}f_ig_j-\sum_{x\oplus (i\space xor \space j)=1}f_ig_j\\&=FWT(H)_x\end{aligned} \]

然后就是转移了

按照或和并的套路,将其分成两段\(F_0,F_1\),那么有

\(FWT(F)=merge(FWT(F_0)+FWT(F_1),FWT(F_0)-FWT(F_1))\)

逆运算也是一样的

代码

void fwtxor(long long *f,int l=0,int r=n-1)
{
    if(r==l)
        return;
    int mid=(l+r)>>1,len=(r-l+1)>>1;
    fwtxor(f,l,mid);fwtxor(f,mid+1,r);
    for(int i=l;i<=mid;i++)
    {
        long long x=f[i],y=f[i+len];
        f[i]=x+y;
        f[i+len]=x-y;
    }
}
void idfwtxor(long long *f,int l=0,int r=n-1)
{
    if(l==r)
        return;
    int mid=(r+l)>>1,len=(r-l+1)>>1;
    for(int i=l;i<=mid;i++)
    {
        long long x=f[i],y=f[i+len];
        f[i]=(x+y)/2;
        f[i+len]=(x-y)/2;
    }
    idfwtxor(f,l,mid);idfwtxor(f,mid+1,r);
}

子集卷积

对于有一类\(dp\)转移,有\(dp_s=\sum dp_x+val_y,x\cup y=s,|x|+|y|=|s|\),可以转换一下

\(dp[i][j]\)表示集合的大小为\(i\),集合为\(j\)

那么有\(dp[i][j]=\begin{cases}dp[j]\space\space \space &|j|=i\\0\space \space \space \space\space&|j|\ne i\end{cases}\)

然后有\(dp[n][m]=\sum_{i=1}^{|m|}\sum_{j|k=m}dp[i][j]+val[n-i][k]\)

内层的\(\sum\)每做完一次之后都要更新一下\(dp\),防止不合法的情况出现,同时,这一层可以用\(FWT\)进行优化,

时间复杂度为\(O(nlog_nlog_n)\)

posted @ 2021-02-03 11:01  loney_s  阅读(146)  评论(0)    收藏  举报