• 【学习笔记】集合并卷积,子集卷积与占位多项式


    集合并卷积,子集卷积与占位多项式

    (FWT)(FMT),集合并卷积

    解决对于

    [c_i=sum_{ioplus j=k}a_ib_j ]

    暴力的枚举子集是(O(3^n))的,特殊运算下可以用所提的两种算法做到(O(n2^n))

    (oplus)(or,and,xor)时用(FWT)都可以解决

    (FMT)只能解决(or,and)的情况,但更容易实现和理解。

    先介绍(FMT)

    (FMT)

    全称为快速莫比乌斯变换。

    我们认为二进制下的(0/1)的意义是集合是否存在元素。

    那就是其实就是集合并与交的一个运算

    (and 为and,or 为or)

    [c_S=sumlimits_{Lsubseteq S}sumlimits_{Rsubseteq S}[Lor or and R=S]f_L*g_R ]

    以并为例,类比于(FFT)的过程,我们引入(f)的莫比乌斯变换(F)

    [F_S=sumlimits_{Tsubseteq S}f_T ]

    它在集合并(交)卷积意义下是可以相乘的。

    因为显然有(Asubseteq S,Bsubseteq S,Aor Bsubseteq S)

    [c_S=sumlimits_{Lsubseteq S}sumlimits_{Rsubseteq S}[Lor R=S]f_L*g_R\ C_S=sumlimits_{Tsubseteq S}sumlimits_{Lsubseteq T}sumlimits_{Rsubseteq T}[Lor R=T]f_L*g_R\ C_S=sumlimits_{Lsubseteq S}sumlimits_{Rsubseteq S}[Lor Rsubseteq S]f_L*g_R\ C_S=(sumlimits_{Lsubseteq S}f_L)(sumlimits_{Rsubseteq S}g_R)\ C_S=F_S*G_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'_{Scup i,i}=F'_{S,i}+F'_{Scup i,i-1}\ ecause i otin S,F'_{S,i}=F'_{S,i-1}\ F'_{Scup i,i}=F'_{S,i-1}+F'_{Scup i,i-1} ]

    逆变换就是逐层减掉即可以得到(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)

    [egin{align} F_c&=sumlimits_{ioplus j=0}c_i-sumlimits_{ioplus j=0}c_j\ &=sumlimits_{ioplus (j operatorname{xor} k)=0}a_jb_k-sumlimits_{ioplus (j operatorname{xor} k)=1}a_jb_k\ &=sumlimits_{(ioplus j)operatorname{xor}(ioplus k)=0}a_jb_k-sumlimits_{(ioplus j)operatorname{xor}(ioplus k)=1}a_jb_k\ &=sumlimits_{ioplus j=0}sumlimits_{ioplus k=0}a_jb_k+sumlimits_{ioplus j=1}sumlimits_{ioplus k=1}a_jb_k-sumlimits_{ioplus j=0}sumlimits_{ioplus k=1}a_jb_k-sumlimits_{ioplus j=1}sumlimits_{ioplus k=0}a_jb_k\ &=(sumlimits_{ioplus j=0}a_j-sumlimits_{ioplus j=1}a_j)(sumlimits_{ioplus k=0}b_k-sumlimits_{ioplus k=1}b_k)\ &=F_a*F_b end{align} ]

    我们再来看如何变换出(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;
    			}
    }
    

    子集卷积

    又叫做不相交集合并。

    [C_S=sumlimits_{Lsubseteq S}sumlimits_{Rsubseteq S}[Lcup R=S][Lcap R=emptyset]A_LB_R ]

    算法一

    考虑([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)较小,可以直接使用暴力求逆。

    即按

    [large g_i=frac{1-sumlimits_{j=0}^{i-1}g_jf_{i-j}}{f_0} ]

    递推即可。

    代码略。

    占位多项式

    回顾我们在子集卷积的时候引入的状态,我们可以对它每一列进行卷积,求逆,一个大胆的想法就产生了。

    我们可不可以把它当成一个多项式,然后就可以使用我们多项式算法里的(ln,exp)呢?

    答案是肯定的。

    我们引入占位多项式来说明,形式化的,我们定义

    [F_{i,j}=egin{cases}f_j&pop(j)=i\0&pop(j) eq iend{cases} ]

    于是我们就可以理解之前我们做(FMT)的意义。

    我们求的实际上,是(H_{i,S})

    (H_{i,S})如何得到的呢,我们枚举组成(S)的两个集合(|L|,|R|)

    对第一维大小就是要求(|L|+|R|=|S|),对于第二维,(L+R=S)其实就是(Lcup R=S)

    于是我们就对第二维做(FMT),然后对第一维卷积。

    最后对我们要求的一列(IFMT)回来就可以得到答案了。

    其他的应用((ln,exp))

    咕咕咕,到时候有人看了(自己学会了)再更新吧。

    欢迎批评指正!

    参考zjp_shadow的博客

    参考VFK的国家队论文《集合幂级数的性质及其快速算法》

    参考xyz32768的博客

    参考xht37 的洛谷博客:题解 P4717【模板】快速沃尔什变换

  • 相关阅读:
    Java GUI图形界面开发工具
    python操作MySQL数据库
    国外五大股票交易系统及其源码
    五个抄底摸高的交易系统和源代码
    海龟交易系统源码
    模式识别话题
    几种常见模式识别算法整理和总结
    比较好的开源人脸识别软件
    利用开源程序(ImageMagick+tesseract-ocr)实现图像验证码识别
    JSONObject与JSONArray的使用
  • 原文地址:https://www.cnblogs.com/LLCSBlog/p/13862723.html
Copyright © 2020-2023  润新知