集合并卷积,子集卷积与占位多项式
(FWT)与(FMT),集合并卷积
解决对于
暴力的枚举子集是(O(3^n))的,特殊运算下可以用所提的两种算法做到(O(n2^n))
当(oplus)为(or,and,xor)时用(FWT)都可以解决
(FMT)只能解决(or,and)的情况,但更容易实现和理解。
先介绍(FMT)
(FMT)
全称为快速莫比乌斯变换。
我们认为二进制下的(0/1)的意义是集合是否存在元素。
那就是其实就是集合并与交的一个运算
(and 为and,or 为or)
以并为例,类比于(FFT)的过程,我们引入(f)的莫比乌斯变换(F)
它在集合并(交)卷积意义下是可以相乘的。
因为显然有(Asubseteq S,Bsubseteq S,Aor Bsubseteq S)
然后我们考虑用(C)反推(c),其实就是简单的容斥原理(large f_S=sumlimits_{Tsubseteq S}(-1)^{|S|-|T|}F_T)
快速变换与逆变换
我们暴力变换的复杂度没有优化,还是(3^n)的
观察(F_S=sumlimits_{Tsubseteq S}f_S),自然的想法产生了,我们按照(T)的大小分层递推。
以并运算为例,令(F'_{S,i})表示考虑了前(i)个元素存不存在,其他元素强制存在后的(F_i),形式化一点就是(sumlimits_{Tsubseteq S} [(S-T)subseteq{emptyset,1,2,...,i}] f_{T})
(F'_{S,0})即为(f_S),(F'_{S,|S|})即为所求
考虑第(i)个元素是否存在
逆变换就是逐层减掉即可以得到(f_S)。
对于交运算,我们考虑的是超集,倒过来逐个考虑有没有被交掉,(F'_{S,i}=F'_{Scup i,i}+F'_{S,i-1})
void FMT_or(int f[],int opt){
for(int i=0;i<n;++i)
for(int S=0;S<(1<<n);++S)
if(S>>i&1)
f[S]+=opt*F[S^(1<<i)];
}
void FMT_and(int f[],int opt){
for(int i=0;i<n;++i)
for(int S=(1<<n);~S;--S)
if(~S>>i&1)
f[S]+=opt*F[S^(1<<i)];
}
顺便提一下,对于并运算,这个操作相当于逐个考虑每一维做高位前缀和,交运算是做高位后缀和,逆运算就是高维差分。
简单的说就是我们逐个考虑每一维的前缀和,对于矩形来说就是先对每一列把纵向的前缀和做了,然后按行累加。
(FWT)
全称为快速沃尔什变换。
(or,and)
经过大佬纠正,这其实本质上就是(FMT)的不同写法
类似于(FMT),我们想得到(F_S=sumlimits_{Tsubseteq S}f_T)这样一个序列。
考虑按(i)的存在性考虑,令(F_{S_0})为不存在(i)的部分序列,(F_{S_1})为考虑存在(i)的部分序列。
(F_S=merge(F_{S_0},F_{S_0}+F_{S_1}))
(merge)的含义是拼接。
逆变换仍然是反过来减掉。
于是我们按序列的长度逐个分治。
inline void _FMT_or(int f[],int n,int opt)
{
for(int l=1;l<n;l<<=1)//l为半长度
for(int i=0;i<n;i+=l<<1)
for(int j=0;j<l;++j)
(f[i+j+l]+=f[i+j]*opt)%=P;
}
(and)显然是加到左边
inline void _FMT_and(int f[],int n,int opt)
{
for(int l=1;l<n;l<<=1)
for(int i=0;i<n;i+=l<<1)
for(int j=0;j<l;++j)
(f[i+j]+=f[i+j+l]*opt)%=P;
}
(xor)
这个在集合上的意义是集合对称差卷积,其实就是异或卷积的意思。
这应该才是真正的(FWT)
直接考虑序列运算。
(C_k={sumlimits_{ioperatorname {xor}j=k}}A_iB_j)
之前优美的莫比乌斯变换的乘法已经失效了。
我们重新定义一个变换。
我们想得和莫比乌斯变换类似的性质,
莫比乌斯变换转换成对应的运算形式,(F_i=sumlimits_{jor i=i}f_j)
当时我们有(jsubseteq i,ksubseteq i ightarrow(jor k)subseteq i)
我们定义一个数(x)二进制位下(1)的个数是(pop(x))
定义(xoplus y = pop(x&y)mod2)
显然有((joplus i)=x,(koplus i)=y,(joperatorname{xor}k)oplus i=xoperatorname{xor}y)
定义(F_i=sumlimits_{ioplus j=0}a_j-sumlimits_{ioplus j=1}a_j)
我们再来看如何变换出(F)
仍然逐个考虑(F_S=merge(F_{S0}+F_{S1},F_{S0}-F_{S1}))
逆变换是(S=merge(frac{S0+S1}2,frac{S0-S1}2))
我们都知道莫比乌斯变换的乘法成立(即(F_C=F_A*F_B))对(or)适用因为(jor i=i,kor i=i ightarrow (jor k)=i),(and)在超集上同理
这些运算本质上是高维前缀和与高维后缀和。
仔细一想,我们发现异或其实就是高维上每一维长度为(2)的离散傅里叶变换,带入单位根其实就是(1)与(-1)。
inline void FWT(int f[],int n,int opt)
{
for(int l=1;l<n;l<<=1)
for(int i=0;i<n;i+=l<<1)
for(int j=0;j<l;++j)
{
int Nx=f[i+j],Ny=f[i+j+l];
f[i+j]=add(Nx+Ny),f[i+j+l]=sub(Nx-Ny);
if(opt==-1)f[i+j+l]=f[i+j+l]*i2%P,f[i+j]=f[i+j]*i2%P;
}
}
子集卷积
又叫做不相交集合并。
算法一
考虑([Lcup R=S][Lcap R=empty]=[Lcup R=S][|L|+|R|=S])
于是我们可以通过增加一维的大小限制来控制。
令(f_{i,S})表示有(i)个元素,集合为(S)的答案。
当(i=|S|)时为(f_S),否则为(0)。
做完(FMT)之后,
将(f_{i,S})与(g_{j,S})卷起来得到(h_{i+j,S})。
最后答案自然就是(h_{pop(S),S})。
复杂度(O(n^22^n))。
代码略。
算法二
考虑([Lcup R=S][Lcap R=empty]=[Loplus R=S][|L|+|R|=S])
这里我们定义(oplus=operatorname {xor}),那么我们也可以同样先取(f)然后做完(FWT)后卷起来。
答案依然是(h_{pop(S),S})。
复杂度(O(n^22^n))
代码略。
子集逆卷积
实质上是上面的逆运算,直接使用多项式除法即可。
这里因为(n)较小,可以直接使用暴力求逆。
即按
递推即可。
代码略。
占位多项式
回顾我们在子集卷积的时候引入的状态,我们可以对它每一列进行卷积,求逆,一个大胆的想法就产生了。
我们可不可以把它当成一个多项式,然后就可以使用我们多项式算法里的(ln,exp)呢?
答案是肯定的。
我们引入占位多项式来说明,形式化的,我们定义
于是我们就可以理解之前我们做(FMT)的意义。
我们求的实际上,是(H_{i,S})
(H_{i,S})如何得到的呢,我们枚举组成(S)的两个集合(|L|,|R|)。
对第一维大小就是要求(|L|+|R|=|S|),对于第二维,(L+R=S)其实就是(Lcup R=S)。
于是我们就对第二维做(FMT),然后对第一维卷积。
最后对我们要求的一列(IFMT)回来就可以得到答案了。
其他的应用((ln,exp))
咕咕咕,到时候有人看了(自己学会了)再更新吧。
欢迎批评指正!
参考VFK的国家队论文《集合幂级数的性质及其快速算法》