芝士: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\)
别管这个函数怎么来的
然后我们尝试将两个这样的函数相乘
那么既有\(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}\)
然后就是转移了
按照或和并的套路,将其分成两段\(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)\)

浙公网安备 33010602011771号