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

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

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

解决对于

\[c_i=\sum_{i\oplus 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=\sum\limits_{L\subseteq S}\sum\limits_{R\subseteq S}[L\or or \and R=S]f_L*g_R \]

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

\[F_S=\sum\limits_{T\subseteq S}f_T \]

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

因为显然有\(A\subseteq S,B\subseteq S,A\or B\subseteq S\)

\[c_S=\sum\limits_{L\subseteq S}\sum\limits_{R\subseteq S}[L\or R=S]f_L*g_R\\ C_S=\sum\limits_{T\subseteq S}\sum\limits_{L\subseteq T}\sum\limits_{R\subseteq T}[L\or R=T]f_L*g_R\\ C_S=\sum\limits_{L\subseteq S}\sum\limits_{R\subseteq S}[L\or R\subseteq S]f_L*g_R\\ C_S=(\sum\limits_{L\subseteq S}f_L)(\sum\limits_{R\subseteq S}g_R)\\ C_S=F_S*G_S \]

然后我们考虑用\(C\)反推\(c\),其实就是简单的容斥原理\(\large f_S=\sum\limits_{T\subseteq S}(-1)^{|S|-|T|}F_T\)

快速变换与逆变换

我们暴力变换的复杂度没有优化,还是\(3^n\)

观察\(F_S=\sum\limits_{T\subseteq S}f_S\),自然的想法产生了,我们按照\(T\)的大小分层递推。

以并运算为例,令\(F'_{S,i}\)表示考虑了前\(i\)个元素存不存在,其他元素强制存在后的\(F_i\),形式化一点就是\(\sum\limits_{T\subseteq S} [(S-T)\subseteq\{\emptyset,1,2,...,i\}] f_{T}\)

\(F'_{S,0}\)即为\(f_S\),\(F'_{S,|S|}\)即为所求

考虑第\(i\)个元素是否存在

\[F'_{S\cup i,i}=F'_{S,i}+F'_{S\cup i,i-1}\\ \because i\notin S,F'_{S,i}=F'_{S,i-1}\\ F'_{S\cup i,i}=F'_{S,i-1}+F'_{S\cup i,i-1} \]

逆变换就是逐层减掉即可以得到\(f_S\)

对于交运算,我们考虑的是超集,倒过来逐个考虑有没有被交掉,\(F'_{S,i}=F'_{S\cup 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=\sum\limits_{T\subseteq 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={\sum\limits_{i\operatorname {xor}j=k}}A_iB_j\)

之前优美的莫比乌斯变换的乘法已经失效了。

我们重新定义一个变换。

我们想得和莫比乌斯变换类似的性质,

莫比乌斯变换转换成对应的运算形式,\(F_i=\sum\limits_{j\or i=i}f_j\)

当时我们有\(j\subseteq i,k\subseteq i\rightarrow(j\or k)\subseteq i\)

我们定义一个数\(x\)二进制位下\(1\)的个数是\(pop(x)\)

定义\(x\oplus y = pop(x\&y)\mod2\)

显然有\((j\oplus i)=x,(k\oplus i)=y,(j\operatorname{xor}k)\oplus i=x\operatorname{xor}y\)

定义\(F_i=\sum\limits_{i\oplus j=0}a_j-\sum\limits_{i\oplus j=1}a_j\)

\[\begin{align} F_c&=\sum\limits_{i\oplus j=0}c_i-\sum\limits_{i\oplus j=0}c_j\\ &=\sum\limits_{i\oplus (j \operatorname{xor} k)=0}a_jb_k-\sum\limits_{i\oplus (j \operatorname{xor} k)=1}a_jb_k\\ &=\sum\limits_{(i\oplus j)\operatorname{xor}(i\oplus k)=0}a_jb_k-\sum\limits_{(i\oplus j)\operatorname{xor}(i\oplus k)=1}a_jb_k\\ &=\sum\limits_{i\oplus j=0}\sum\limits_{i\oplus k=0}a_jb_k+\sum\limits_{i\oplus j=1}\sum\limits_{i\oplus k=1}a_jb_k-\sum\limits_{i\oplus j=0}\sum\limits_{i\oplus k=1}a_jb_k-\sum\limits_{i\oplus j=1}\sum\limits_{i\oplus k=0}a_jb_k\\ &=(\sum\limits_{i\oplus j=0}a_j-\sum\limits_{i\oplus j=1}a_j)(\sum\limits_{i\oplus k=0}b_k-\sum\limits_{i\oplus 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\)适用因为\(j\or i=i,k\or i=i\rightarrow (j\or 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=\sum\limits_{L\subseteq S}\sum\limits_{R\subseteq S}[L\cup R=S][L\cap R=\emptyset]A_LB_R \]

算法一

考虑\([L\cup R=S][L\cap R=\empty]=[L\cup 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)\)

代码略。

算法二

考虑\([L\cup R=S][L\cap R=\empty]=[L\oplus R=S][|L|+|R|=S]\)

这里我们定义\(\oplus=\operatorname {xor}\),那么我们也可以同样先取\(f\)然后做完\(FWT\)后卷起来。

答案依然是\(h_{pop(S),S}\)

复杂度\(O(n^22^n)\)

代码略。

子集逆卷积

实质上是上面的逆运算,直接使用多项式除法即可。

这里因为\(n\)较小,可以直接使用暴力求逆。

即按

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

递推即可。

代码略。

占位多项式

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

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

答案是肯定的。

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

\[F_{i,j}=\begin{cases}f_j&pop(j)=i\\0&pop(j)\neq i\end{cases} \]

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

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

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

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

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

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

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

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

欢迎批评指正!

参考zjp_shadow的博客

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

参考xyz32768的博客

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

posted @ 2020-10-23 10:35  Adscn  阅读(427)  评论(0编辑  收藏  举报