集合并卷积的三种求法

也许更好的阅读体验

本文主要内容是对武汉市第二中学吕凯风同学的论文《集合幂级数的性质与应用及其快速算法》的理解

定义

集合幂级数

为了更方便的研究集合的卷积,引入集合幂级数的概念
集合幂级数也是形式幂级数的一种,只是集合的一种表现形式,无需考虑收敛或发散的含义

定义一个集合 \(S\) 的集合幂级数为 \(f\) ,那么我们就可以把集合 \(S\) 表示为如下形式

\(\begin{aligned}f=\sum _{T\subseteq S}f_{T}\cdot x^{T}\end{aligned}\)
\(f_T\)为\(T\)这个集合幂级数的系数
简单来说就是用二进制表示集合的元素是否存在,并将其写成多项式的形式

约定

\(c=\left(a,b\right)\)表示将\(a,b\)连起来组成\(c\)
为了方便,将\(f*g\)写成\(fg\)

卷积运算

加法

\(\begin{aligned}h=f+g\end{aligned}\)
那么
\(h_S=f_S+g_S\)

减法

\(\begin{aligned}h=f-g\end{aligned}\)
那么
\(h_S=f_S-g_S\)

乘法

\(\begin{aligned}h=f*g\end{aligned}\)
那么
\(\begin{aligned}h_S=\sum_{i°j\subseteq S}f_ig_j\end{aligned}\)
其中\(°\)可以是与,或,异或运算

集合并卷积 就是\(°\)进行或运算
子集卷积 就是\(°\)进行与运算
集合对称差卷积 就是\(°\)进行异或运算


快速求法

加法和减法都可以在\(O(n)\)时间复杂度内求出结果
对乘法,有一些优化的算法,以集合并卷积为例

分治

设\(f\)有\(2^n\)项
考虑将其集合幂级数的第\(n\)个元素提出来
则\(f=f^-+x^{\{n\}}f^+\),可以知道\(f^-\)为前\(2^{n-1}\)项,\(f^+\)为后\(2^{n-1}\)项即\(f^-\)的第\(n\)个元素在二进制下为\(0\),\(f^+\)的第\(n\)个元素在二进制下为\(1\)
\(\begin{aligned}fg&=\left( f^-+x^{\{n\}}f^{+}\right) \left( g^{-}+x^{\{n\}}g^{+}\right)\\ &=f^-g^-+x^{\{n\}}\left( f^-g^{+}+f^{+}g^-+f^{+}g^{+}\right) \\ &=f^-g^-+x^{\{n\}}\left(\left(f^-+f^+\right)\left(g^-+g^+\right)-f^-g^-\right) \end{aligned}\)
这样计算\(fg\)就只要计算\(f^-g^-\)和\(\left(f^-+f^+\right)\left(g^-+g^+\right)\)了
此时已经没有\(n\)这个元素了,因为
于是我们可以递归分治求解\(fg\)
时间复杂度\(O\left(n2^n\right)\)

\(\mathcal{Code}\)

void fold (int *f,int *g,int *h,int hlen)//hlen -> half len
{
    if (hlen==1)    return void(h[0]=f[0]*g[0]);
    for (int i=0;i<hlen;++i)    f[i+hlen]+=f[i],g[i+hlen]+=g[i];
    fold(f,g,h,hlen>>1),fold(g+hlen,g+hlen,h+hlen,hlen>>1);
    for (int i=0;i<hlen;++i)    f[i+hlen]-=f[i],g[i+hlen]-=g[i];
}

快速莫比乌斯变换(FMT)

对于一个集合幂级数\(f\),我们定义其快速莫比乌斯变换为集合幂级数\(\widehat f\),使其系数满足
\(\begin{aligned}\widehat f_S=\sum_{T\subseteq S}f_{T}\end{aligned}\)

由容斥原理,我们可以得到
\(\begin{aligned}f_S=\sum_{T\subseteq S}\left(-1\right)^{|S|-|T|}\widehat f_T\end{aligned}\)

考虑乘法\(\widehat h=\widehat f\widehat g\)
\(\begin{aligned}\widehat h_{s}&=\sum _{i\subseteq S}\sum _{j\subseteq S}f_{i}g_{j}\\ &=\left(\sum _{i\subseteq S}f_i\right)\left(\sum _{j\subseteq S}g_{j}\right)\\ &=\widehat f_S \widehat g_S \end{aligned}\)

那么,现在我们知道想办法怎么求\(\widehat f\)和\(\widehat g\),然后把它们的系数乘起来,就可以得到\(\widehat h\)
然后再将其反演得到\(f\)(因为容斥是肯定会超时的)

考虑递推
我们设\(\widehat f_S^{\left(i\right)}\)使其满足
\(\begin{aligned}\widehat f_S^{\left(i\right)}=\sum _{T\subseteq S}\left[ \left( S-T\right) \subseteq \left\{ 1,2,\ldots ,i\right\} \right] f_{T}\end{aligned}\)
即若\(i+1\sim n\)有元素属于\(S\),则必须要选择,而\(1\sim i\)中的元素可有可无
那么我们最终的\(\widehat f_S=\widehat f_S^{\left(n\right)}\),所有的元素都是可有可无的,即它的子集都被包含在内了
考虑第\(S\)中有没有\(i\)这个元素

  • 没有,则\(\widehat f_S^{\left(i\right)}=\widehat f_S^{\left(i-1\right)}\)
  • 有,那么\(\widehat f^{\left( i\right) }_{S}=\widehat f^{\left( i-1\right) }_{S}+\widehat f^{\left( i-1\right) }_{S- i}\),\(S-i\)表示从\(S\)这个集合中去掉\(i\)这个元素,这个式子的后两项前者是第\(i\)个元素一定被选了,后者则是第\(i\)个元素一定没有被选

而要将其反演,我们考虑其逆过程,只需将所有加上来的全部减去即可

时间复杂度\(O\left(n2^n\right)\)

\(\mathcal{Code}\)

上面做法都是二维数组
考虑先枚举\(i\)再算所有\(S\)的答案,只需一维数组即可

void FMT (int *a,int n)//n个元素
{
    int all=1<<n;
    for (int i=0;i<n;++i)
        for (int j=0;j<all;++j)
            if (j>>1&1) a[j]+=a[j^(1<<i)];
}

void IFMT (int *a,int n)//n个元素
{
    int all=1<<n;
    for (int i=0;i<n;++i)
        for (int j=0;j<all;++j)
            if (j>>i&1) a[j]-=a[j^(1<<i)];
}

快速沃尔什变换(FWT)

我们发现,在进行分治算法中,只需保留出\(f^-,g^-\),\(\left(f^-+f^+\right),\left(g^-+g^+\right)\)就可以算出答案了
可惜递归的常数相对来说太大,我们考虑将其写成循环的形式就可以得到\(FWT\)了

若不考虑分治写成循环,我们换一种方法理解\(FWT\),当然,这是另一种思路了,上面将递归改成循环的思路是正确的
上面的\(f=f^-+f^+\),我们是始终让其满足这个条件的,所以在后面还减去了一个\(f^-g^-\)
让我们跳出思维的局限,弄这么一个\(f',g'\)使其满足\((fg)'=f'g'\),这样我们只要计算\(f'g'\),然后把它反演一下就可以得到\(fg\)
这里呢
\(f'=\left(f^-,f^-+f^+\right)\)
为什么这样就可以呢
考虑\((fg)'\),根据上面的推导
\(\left(fg\right)'=\left(f^-g^-,\left(f^-+f^+\right)\left(g^-+g^+\right)\right)\)
再考虑\(f'g'\)
\(f'^-g'^-=f^-g^-\)
\(f'^+g'^+=\left(f^-+f^+\right)\left(g^-+g^+\right)\)
所以这样是可以的
于是我们可以得到
\(f'=\left(f^-,f^-+f^+\right)\)
然后称这样的\(f'\)叫做沃尔什变换
\(FWT\left(f\right)=FWT\left(f^-,f^-+f^+\right)\)

反演也很简单
即将多算的\(f^-\)减去即可

\(\mathcal{Code}\)

void FWT (int *a,int n)
{
    for (int len=2;len<=n;len<<=1)
        for (int i=0,hlen=len>>1;i<n;i+=len)
            for (int j=i,k=j+hlen;j<k;++j)
                a[j+hlen]+=a[j];
}

void IFWT (int *a,int n)
{
    for (int len=2;len<=n;len<<=1)
        for (int i=0,hlen=len>>1;i<n;i+=len)
            for (int j=i,k=j+hlen;j<k;++j)
                a[j+hlen]-=a[j];
}

时间复杂度\(O\left(n2^n\right)\)

如有哪里讲得不是很明白或是有错误,欢迎指正
如您喜欢的话不妨点个赞收藏一下吧
如能得到推荐博主就更开心了
您的鼓励是博主的动力

原文地址:https://www.cnblogs.com/Morning-Glory/p/11332909.html

时间: 2024-11-10 19:11:42

集合并卷积的三种求法的相关文章

卷积的三种模式:full, same, valid

通常用外部api进行卷积的时候,会面临mode选择. 本文清晰展示三种模式的不同之处,其实这三种不同模式是对卷积核移动范围的不同限制. 设 image的大小是7x7,filter的大小是3x3 1,full mode 橙色部分为image, 蓝色部分为filter.full模式的意思是,从filter和image刚相交开始做卷积,白色部分为填0.filter的运动范围如图所示. 2,same mode 当filter的中心(K)与image的边角重合时,开始做卷积运算,可见filter的运动范围

卷积的三种模式:full、same、valid + 卷积输出size的计算

转自https://blog.csdn.net/u012370185/article/details/95238828 通常用外部api进行卷积的时候,会面临mode选择. 这三种mode的不同点:对卷积核移动范围的不同限制. 设 image的大小是7x7(橙色部分),filter的大小是3x3(蓝色部分) 1. full mode full mode:从filter和image刚相交开始做卷积,不足的部分padding 0.filter的运动范围如图所示. 2. same mode same

java中遍历集合的三种方式

集合遍历操作的三种方式 Iterator迭代器方式 增强for循环 普通for循环 代码如下: public static void test3(){ ArrayList list = new ArrayList(); list.add(123); list.add("AAAA"); list.add("bb"); list.add(new String("JavaEE")); list.add(new Date()); list.add(fal

LCS的几种求法

\(LCS:\) 对于两个长度均为 \(N\) 的数列 \(A\) 和 \(B\) ,存在一个数列 \(C\) 使得 \(C\) 既是 \(A\) 的子序列有事 \(B\) 的子序列,现在需要求这个数列的最长的长度,这就是最长公共子序列. \(solution\quad 1:\) 这道题是世界上最经典的DP题之一,我们可以知道我们做需要求的子序列中的任意一个元素在 \(A\) 和 \(B\) 中都存在,所以我们可以设出状态即 \(F[i][j]\) 表示我们已经匹配了 \(A\) 的前 \(i\

使用三种不同循环结构对100以内偶数求和

?分析思路 在循环里面增加约束,使累加1变成累加2 1 package com.循环; 2 3 /** 4 * 功能描述: 5 * Java使用三种不同循环结构对100以内偶数求和 6 * 7 * @Author: apple. 8 * @Date: 2019/8/14 3:36 PM 9 */ 10 public class Demo { 11 public static void main(String[] args) { 12 int i = 0; 13 int sum = 0; 14 /

Java中List集合遍历的三种方式

首先创建一个List集合: List<String> list = new ArrayList<String>();list.add("name"); list.add("age"); list.add("sex"); 第一种:利用集合的size()方法遍历for(int i= 0:i<list.size();i++){ list.get(i);} 第二种:for增强循环for(String string : str

HashTable集合遍历的三种方法

hashtable集合遍历可以根据key,value以及key+value 示例代码: Hashtable table = new Hashtable(); Student stu = new Student(); stu.Name = "李四"; stu.Age = 18; Student stu1 = new Student(); stu1.Name = "张三"; stu1.Age = 18; Student stu2 = new Student(); stu

三种形式遍历集合

对于遍历集合获取其对象,在这里总结的三种简单的方式 方式一 : 将集合变为数组,后遍历数组 Object[] obj = list.toArray(); for(Object s : obj){ System.out.println((String) s); } 方式二 :  get()方法获取 . 但只能在list集合中使用, 只有List集合才有索引值. for(int i = 0;i<list.size();i++){ System.out.println(list.get(i)); }

关于JAVA中HashMap集合的的三种超不好记的便利方案

HashMap 和 HashSet 是 Java Collection Framework 的两个重要成员,其中 HashMap 是 Map 接口的常用实现类 1:先创建一个类 1 package Day; 2 3 import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java