引入
对于两个多项式,形如
a
0
×
x
0
+
a
1
×
x
1
+
⋯
+
a
n
×
x
n
a_0\times x^0+a_1\times x^1+\dots+a_n\times x^n
a0×x0+a1×x1+⋯+an×xn
我们将其记为
f
(
x
)
f(x)
f(x)与
g
(
x
)
g(x)
g(x),当我们需要快速求
h
(
x
)
=
f
(
x
)
∗
g
(
x
)
h(x)=f(x)*g(x)
h(x)=f(x)∗g(x)(卷积)时,我们就会用
F
F
T
\rm FFT
FFT算法来解决这个问题。
而
F
W
T
\rm FWT
FWT就是来解决集合上的一类卷积问题。
- 前言
在比赛与平时练题时,我们常常会遇到一些关于集合的动态规划的问题,比如集合的方案数计数,某些集合的权值计算等,但是一般推出的递推式复杂度会成指数或者阶乘之高,是无法忍受的,但是又没有好的解决方法,于是就放弃去打爆力算了。
那么对于两个集合,形如
{
a
0
,
a
1
,
a
2
,
…
,
a
n
}
\{a_0,a_1,a_2,\dots,a_n\}
{a0,a1,a2,…,an}
我们将其记为
A
A
A与
B
B
B,现在又两个分别定义在
A
A
A和
B
B
B的函数
f
f
f与
g
g
g,现有另一个定义在集合上的函数
h
h
h。对于一个集合
S
S
S,
h
S
h_S
hS的计算方式如下,我们用
O
O
O表示全集,其中
A
⊆
O
,
B
⊆
O
,
S
⊆
O
A\subseteq O,B\subseteq O,S \subseteq O
A⊆O,B⊆O,S⊆O。
h
S
=
∑
I
⊆
O
∑
J
⊆
O
[
I
⨂
J
=
S
]
f
I
×
g
J
h_S=\sum_{I\subseteq O}\sum_{J\subseteq O}[I\bigotimes J=S]f_I\times g_J
hS=I⊆O∑J⊆O∑[I⨂J=S]fI×gJ
这里 ⨂ \bigotimes ⨂为一类集合的运算,一般为 ⋃ ⋂ ⨁ \bigcup \bigcap \bigoplus ⋃⋂⨁(交,并,对称差),这个便是集合的卷积。
那么有没有类似FFT的方法快速计算两个集合之间的卷积呢?若有不就可以解决前言中的问题了?答案肯定是有的。
前置
集合幂级数
对数列理论熟悉的读者应该知道,我们可以把一个数列 f 0 , f 1 , f 2 , … f_0,f_1,f_2,\dots f0,f1,f2,…和一个形式幂级数 f ( x ) = ∑ 0 ≤ k f k × x k f(x)=\sum_{0≤k}\ f_k\times x^k f(x)=∑0≤k fk×xk相对应,称之为该数列的生成函数。由于有很多相关的理论与快速算法,这个方法就能解决很多的数列的计算问题。
那么我们对于集合也定义类似的东西,看看能否达到同样的效果。
设 F F F是一个域,则称函数 f : O − > F f:O\ ->\ F f:O −> F为 F F F上的一个集合幂级数,对于一个集合 S ( S ⊆ O ) S(S\subseteq O) S(S⊆O),我们令 f S f_S fS为把 S S S带入函数 f f f时的函数值,同样的将 f S f_S fS称为该集合幂级数的第 S S S项的系数。那么如果所有的 f S f_S fS确定了的话,该函数 f f f也就确定了。
那么我们设
f
,
g
f,g
f,g为集合幂级数,定义加法
f
+
g
=
h
f+g=h
f+g=h,
h
h
h也是一个集合幂级数,且对于一个集合
S
S
S,满足如下式子:
h
S
=
f
S
+
g
S
h_S=f_S+g_S
hS=fS+gS
所以,集合幂级数的加法就是对应系数相加,减法同理,改为 f S − g S f_S-g_S fS−gS即可。
下面我们定义集合幂级数的具体形式。参照形式幂级数的样子,我们定义如下: 对于任意的 v ∈ F , S ⊆ O v\in F,S\subseteq O v∈F,S⊆O,我们用 v × x S v\times x^S v×xS表示 f f f函数中的第 S S S项的系数为 v v v。
那么对于一个集合幂级数的函数
f
f
f,形式如下:
f
=
∑
S
⊆
O
f
S
×
x
S
f=\sum_{S\subseteq O}f_S\times x^S
f=S⊆O∑fS×xS
f S × x S f_S\times x^S fS×xS一般简写为 f S x S f_Sx^S fSxS
我们以此来表示一个集合幂级数。
举个栗子,当 O = { 1 , 2 } , F = R O=\{1,2\},F=R O={1,2},F=R( R R R表示实数集),对于该函数 f f f,我们可以用 f ( x ) = 3 x ∅ + 7 x { 1 } + 6 x { 2 } + 9 x { 1 , 2 } f(x)=3x^{∅}+7x^{\{1\}}+6x^{\{2\}}+9x^{\{1,2\}} f(x)=3x∅+7x{1}+6x{2}+9x{1,2}来表示一个对应系数为 3 , 7 , 6 , 9 3,7,6,9 3,7,6,9的集合幂级数。
我们同样定义乘法如下,对于集合幂级数 h , f , g h,f,g h,f,g,其中 h = f × g h=f\times g h=f×g,且其中的每一项满足 ( f I x I ) × ( g J x J ) = ( f I × g J ) x I × J \left(f_Ix^I\right)\times \left(g_Jx^J\right)=(f_I\times g_J)x^{I\times J} (fIxI)×(gJxJ)=(fI×gJ)xI×J,那么这个乘法就可以满足交换,结合,分配等定律了。
集合的并,交,对称差卷积
并
我们令三个集合幂级数
f
,
g
,
h
f,g,h
f,g,h,其中
h
h
h满足如下式子:
h
S
=
∑
I
⊆
O
∑
J
⊆
O
[
I
⋃
J
=
S
]
f
I
×
g
J
h_S=\sum_{I\subseteq O}\sum_{J\subseteq O}[I \bigcup J = S]f_I\times g_J
hS=I⊆O∑J⊆O∑[I⋃J=S]fI×gJ
那么我们称 h h h为 f , g f,g f,g的集合并卷积,简记为 f ⨀ g = h f\bigodot g=h f⨀g=h。
交
我们令三个集合幂级数
f
,
g
,
h
f,g,h
f,g,h,其中
h
h
h满足如下式子:
h
S
=
∑
I
⊆
O
∑
J
⊆
O
[
I
⋂
J
=
S
]
f
I
×
g
J
h_S=\sum_{I\subseteq O}\sum_{J\subseteq O}[I \bigcap J = S]f_I\times g_J
hS=I⊆O∑J⊆O∑[I⋂J=S]fI×gJ
那么我们称 h h h为 f , g f,g f,g的集合交卷积,简记为 f ⨀ g = h f\bigodot g=h f⨀g=h。
对称差
我们令三个集合幂级数
f
,
g
,
h
f,g,h
f,g,h,其中
h
h
h满足如下式子:
h
S
=
∑
I
⊆
O
∑
J
⊆
O
[
I
⨁
J
=
S
]
f
I
×
g
J
h_S=\sum_{I\subseteq O}\sum_{J\subseteq O}[I \bigoplus J = S]f_I\times g_J
hS=I⊆O∑J⊆O∑[I⨁J=S]fI×gJ
那么我们称 h h h为 f , g f,g f,g的集合对乘差卷积,简记为 f ⋅ g = h f\cdot g=h f⋅g=h或者 f × g f\times g f×g(能看懂就行)。
对于如上的三个卷积,显然都可 O ( ∣ O ∣ 2 ) O(|O|^2) O(∣O∣2)的时间复杂度计算(括号外面的 O O O为时间复杂度的符号,里面的 O O O表示全集, ∣ O ∣ |O| ∣O∣表示全集大小)。
但是这样肯定不能解决大多数问题,那么我们需要考虑快速算法。
分治是一种降低复杂度的好方法,那么这个能否分治呢?
我们将全集 O O O中的一个元素 w w w单独提出,也就是对于所有含 w w w的集合提一个 w {w} w出来,那么 f f f可以写成由两个集合组合出来的函数, f − f^- f−表示不含 w {w} w的部分, f + f^+ f+表示含有 w {w} w的部分,那么 f = f − + x { w } f + f=f^-+x^{\{w\}}f^+ f=f−+x{w}f+,由此我们可以把原式写成如下形式 f × g = ( f − + x { w } f + ) × ( g − + x { w } g + ) f\times g=\left(f^-+x^{\{w\}}f^+\right)\times \left(g^-+x^{\{w\}}g^+\right) f×g=(f−+x{w}f+)×(g−+x{w}g+)
对于不同的 ⨀ \bigodot ⨀,我们可以将其化简成不同的形式。
- ⨀ = ⋃ \bigodot=\bigcup ⨀=⋃时
由于当前的运算为并,那么 x { w } × x { } = x { w } x^{\{w\}}\times x^{\{\}}=x^{\{w\}} x{w}×x{}=x{w}, x { w } × x { w } = x { w } x^{\{w\}}\times x^{\{w\}}=x^{\{w\}} x{w}×x{w}=x{w}, x { } × x { } = x { } x^{\{\}}\times x^{\{\}}=x^{\{\}} x{}×x{}=x{}(有元素 w w w的与没有的并起来就有元素 w w w,都有元素 w w w并起来还是有,都没有并起来就还是没有)。
所以我们进行如下化简:
f
×
g
=
(
f
−
+
x
{
w
}
f
+
)
×
(
g
−
+
x
{
w
}
g
+
)
f\times g=\left(f^-+x^{\{w\}}f^+\right)\times \left(g^-+x^{\{w\}}g^+\right)
f×g=(f−+x{w}f+)×(g−+x{w}g+)
=
f
−
×
g
−
+
x
{
w
}
×
(
f
−
×
g
+
)
+
x
{
w
}
×
(
f
+
×
g
−
)
+
x
{
w
}
×
(
f
+
×
g
+
)
=f^-\times g^-+x^{\{w\}}\times \left(f^-\times g^+\right)+x^{\{w\}}\times \left(f^+\times g^-\right)+x^{\{w\}}\times \left(f^+\times g^+\right)
=f−×g−+x{w}×(f−×g+)+x{w}×(f+×g−)+x{w}×(f+×g+)
这里将乘号略写
=
f
−
g
−
+
x
{
w
}
(
f
−
g
+
+
f
+
g
−
+
f
+
g
+
)
=f^-g^-+x^{\{w\}}\left(f^-g^+\ + f^+g^-+f^+g^+\right)
=f−g−+x{w}(f−g+ +f+g−+f+g+)
将其化简为乘积的形式
=
f
−
g
−
+
x
{
w
}
(
(
f
−
+
f
+
)
×
(
g
−
+
g
+
)
−
f
−
g
−
)
=f^-g^-+x^{\{w\}}\left(\left(f^-+f^+\right)\times \left(g^-+g^+\right)-f^-g^-\right)
=f−g−+x{w}((f−+f+)×(g−+g+)−f−g−)
我们将其看作两种形式,第一种为单独的
f
−
f^-
f−或
g
−
g^-
g−,另一种为单独的
(
f
−
+
f
+
)
(f^-+f^+)
(f−+f+)或
(
g
−
+
g
+
)
(g^-+g^+)
(g−+g+),那么我们通过分治的方法,先将
f
f
f和
g
g
g分别变化成
f
‘
=
(
f
−
,
(
f
−
+
f
+
)
)
f^`=\left(f^-,\left(f^-+f^+\right)\right)
f‘=(f−,(f−+f+))和
g
‘
=
(
g
−
,
(
g
−
+
g
+
)
)
g^`=\left(g^-,\left(g^-+g^+\right)\right)
g‘=(g−,(g−+g+))
然后对应的项乘起来,我们令
h
‘
=
f
‘
×
g
‘
h^`=f^`\times g^`
h‘=f‘×g‘,此时
h
‘
=
(
h
−
,
h
+
)
=
(
f
−
g
−
,
(
f
−
+
f
+
)
×
(
g
−
+
g
+
)
)
h^`=(h^-,h^+)=(f^-g^-,(f^-+f^+)\times (g^-+g^+))
h‘=(h−,h+)=(f−g−,(f−+f+)×(g−+g+)),但是此时的
h
‘
h^`
h‘并不是我们要求的
h
h
h,联想
F
F
T
\rm FFT
FFT的做法,既然变换过去了,肯定要找个方法变换回来。再看我们推导化简式子的最后有一个
(
(
f
−
+
f
+
)
×
(
g
−
+
g
+
)
−
f
−
g
−
)
\left(\left(f^-+f^+\right)\times \left(g^-+g^+\right)-f^-g^-\right)
((f−+f+)×(g−+g+)−f−g−),所以我们将
h
‘
h^`
h‘变成
h
=
(
h
−
,
h
+
−
h
−
)
h=(h^-,h^+-h^-)
h=(h−,h+−h−),就可以得到
h
h
h了。
复杂度分析,我们令 s i z e size size为元素种类的大小,设总的变换的时间复杂度为 T ( s i z e ) T(size) T(size),可得 T ( s i z e ) = 2 × T ( s i z e − 1 ) + O ( ∣ O ∣ ) T(size)=2\times T(size-1)+O(|O|) T(size)=2×T(size−1)+O(∣O∣),其中 2 × T ( s i z e − 1 ) 2\times T(size-1) 2×T(size−1)是因为提出一个元素后,元素种类减少1,分成了本来有该元素和本来无该元素的两部分,所以乘2,再加上当前这次变换的复杂度。最后解得总复杂度 T ( s i z e ) = O ( s i z e × ∣ O ∣ ) T(size)=O(size\times |O|) T(size)=O(size×∣O∣),而 s i z e size size大小是远远小于由 s i z e size size个元素组成的集合的全集 O O O的大小的,一般 ∣ O ∣ = 2 s i z e |O|=2^{size} ∣O∣=2size,复杂度远远低于暴力复杂度,可以看做 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)。
说明:例如当前 s i z e = 3 size=3 size=3,有 1 , 2 , 3 1,2,3 1,2,3元素,那么 O = { { ∅ } , { 1 } , { 2 } , { 3 } , { 1 , 2 } , { 1 , 3 } , { 2 , 3 } , { 1 , 2 , 3 } } O=\{\{∅\},\{1\},\{2\},\{3\},\{1,2\},\{1,3\},\{2,3\},\{1,2,3\}\} O={{∅},{1},{2},{3},{1,2},{1,3},{2,3},{1,2,3}},所以 O O O的大小为 2 s i z e = 8 2^{size}=8 2size=8。
- ⨀ = ⋂ \bigodot=\bigcap ⨀=⋂时
同样对于交,我们化简原式,过程略,得到如下式子:
乘号略写公式太难打了QAQ。
f
⋅
g
=
f
−
g
−
+
f
+
g
−
+
f
−
g
+
+
x
{
w
}
(
g
+
f
+
)
f\cdot g=f^-g^-+f^+g^-+f^-g^++x^{\{w\}}(g^+f^+)
f⋅g=f−g−+f+g−+f−g++x{w}(g+f+)
说明:因为交是必须两个都有 w {w} w元素,最后才会有,否则就没有 w {w} w元素。
写成乘积形式
=
(
(
f
−
+
f
+
)
⋅
(
g
−
+
g
+
)
−
f
+
g
+
)
+
x
{
w
}
(
g
+
f
+
)
=\left((f^-+f^+)\cdot(g^-+g^+)-f^+g^+\right)+x^{\{w\}}(g^+f^+)
=((f−+f+)⋅(g−+g+)−f+g+)+x{w}(g+f+)
同样的,提出类似的部分后,我们可以对 f , g f,g f,g先自己变换,然后相乘(对应项),然后再变换一次变回来就可以求出 h h h。
其中 f ‘ = ( ( f − + f + ) , f + ) , g ‘ = ( ( g − + g + ) , g + ) , h = ( h − − h + ) , h + f^`=((f^-+f^+),f^+),g^`=((g^-+g^+),g^+),h=(h^--h^+),h^+ f‘=((f−+f+),f+),g‘=((g−+g+),g+),h=(h−−h+),h+
复杂度同上。
- ⨀ = ⨁ \bigodot=\bigoplus ⨀=⨁时
对于对称差,都是大同小异。
化简后得到
f
⋅
g
=
(
f
−
g
−
+
f
+
g
+
)
⋅
x
{
w
}
(
f
−
g
+
+
f
+
g
−
)
f\cdot g=(f^-g^-+f^+g^+)\cdot x^{\{w\}}(f^-g^++f^+g^-)
f⋅g=(f−g−+f+g+)⋅x{w}(f−g++f+g−)
说明:因为对称差计算有点奇怪
好吧其实也不奇怪,两者都有或者都没有该元素,计算出来就没有。只要其中一个有另一个没有,计算出来就有。
所以变换为:
f
‘
=
(
(
f
−
+
f
+
)
,
(
f
−
−
f
+
)
)
f^`=((f^-+f^+),(f^--f^+))
f‘=((f−+f+),(f−−f+))
g
‘
g^`
g‘同理。
h = ( ( h − + h + ) 2 , ( h − − h + ) 2 ) h=(\frac{(h^-+h^+)}{2},\frac{(h^--h^+)}{2}) h=(2(h−+h+),2(h−−h+))
自己手推一下应该可以得到。
- 对称差的另外一种推导方法
我们这么半天,集合幂级数的性质也没太明显用,下面就将对称差的另外一种推导方法。
首先引入如下式子:
对于一个集合
S
S
S有:
1
2
s
i
z
e
∑
T
⊆
O
(
−
1
)
∣
S
⋂
T
∣
=
[
S
=
∅
]
\frac{1}{2^{size}}\sum_{T \subseteq O}(-1)^{|S\bigcap T|}=[S=∅]
2size1T⊆O∑(−1)∣S⋂T∣=[S=∅]
对于证明我们较容易发现,当 S = ∅ S=∅ S=∅时 ∣ S ⋂ T ∣ |S\bigcap T| ∣S⋂T∣恒为 0 0 0,那么 ( − 1 ) ∣ S ⋂ T ∣ (-1)^{|S\bigcap T|} (−1)∣S⋂T∣也就恒为1,又因为 ∣ O ∣ = 2 s i z e |O|=2^{size} ∣O∣=2size,所以最后除以了一个 2 s i z e 2^{size} 2size,式子的值就为1。对于 S S S不为空集,令一个元素 v ∈ S v\in S v∈S,由于 ( − 1 ) ∣ S ⋂ ( T ⨁ { v } ) ∣ (-1)^{|S\bigcap (T\bigoplus \{v\})|} (−1)∣S⋂(T⨁{v})∣,根据对称差的分配律 S ⋂ ( T ⨁ { v } ) = ( S ⨁ { v } ) ⋂ ( T ⨁ { v } ) = ( S ⋂ T ) ⨁ { v } S\bigcap (T\bigoplus \{v\})=(S\bigoplus \{v\})\bigcap (T\bigoplus \{v\})=(S\bigcap T)\bigoplus \{v\} S⋂(T⨁{v})=(S⨁{v})⋂(T⨁{v})=(S⋂T)⨁{v},原式等于 ( − 1 ) ∣ ( S ⋂ T ) ⨁ { v } ∣ (-1)^{|(S\bigcap T)\bigoplus\{v\}|} (−1)∣(S⋂T)⨁{v}∣,又因为 S ⋂ T S\bigcap T S⋂T中没有 { v } \{v\} {v}交集大小增加1,有 { v } \{v\} {v}交集大小减少1,所以怎么都要变化1,所以 ( − 1 ) ∣ ( S ⋂ T ) ⨁ { v } ∣ = − ( − 1 ) ∣ S ⋂ T ∣ (-1)^{|(S\bigcap T)\bigoplus \{v\}|}=-(-1)^{|S\bigcap T|} (−1)∣(S⋂T)⨁{v}∣=−(−1)∣S⋂T∣,那么可以得知 ( − 1 ) ∣ S ⋂ T ∣ + ( − 1 ) ∣ ( S ⋂ T ) ⨁ { v } ∣ = 0 (-1)^{|S\bigcap T|}+(-1)^{|(S\bigcap T)\bigoplus \{v\}|}=0 (−1)∣S⋂T∣+(−1)∣(S⋂T)⨁{v}∣=0,由于 T ⨁ { v } ⨁ { v } = T T\bigoplus \{v\} \bigoplus \{v\} = T T⨁{v}⨁{v}=T,所以 T T T与 T ⨁ { v } T\bigoplus \{v\} T⨁{v}一一对应,所以最后原式的值肯定为0。
我们接下来看:
h = f ⋅ g h=f\cdot g h=f⋅g
对于一个集合
S
S
S,可知
h
S
=
∑
I
⊆
O
∑
J
⊆
O
[
I
⨁
J
=
S
]
f
I
⋅
g
J
h_S=\sum_{I\subseteq O}\sum_{J\subseteq O}[I \bigoplus J=S]f_I\cdot g_J
hS=I⊆O∑J⊆O∑[I⨁J=S]fI⋅gJ
=
∑
I
⊆
O
∑
J
⊆
O
[
I
⨁
J
⨁
S
=
∅
]
f
I
⋅
g
J
=\sum_{I\subseteq O}\sum_{J\subseteq O}[I\bigoplus J\bigoplus S=∅]f_I\cdot g_J
=I⊆O∑J⊆O∑[I⨁J⨁S=∅]fI⋅gJ
=
∑
I
⊆
O
∑
J
⊆
O
1
2
s
i
z
e
∑
T
⊆
O
(
−
1
)
∣
(
I
⨁
J
⨁
S
)
⋂
T
∣
f
I
⋅
g
J
=\sum_{I\subseteq O}\sum_{J\subseteq O}\frac{1}{2^{size}}\sum_{T \subseteq O}(-1)^{|(I\bigoplus J\bigoplus S)\bigcap T|}\ f_I\cdot g_J
=I⊆O∑J⊆O∑2size1T⊆O∑(−1)∣(I⨁J⨁S)⋂T∣ fI⋅gJ
=
∑
I
⊆
O
∑
J
⊆
O
1
2
s
i
z
e
∑
T
⊆
O
(
−
1
)
∣
T
⋂
I
∣
(
−
1
)
∣
T
⋂
J
∣
(
−
1
)
∣
T
⋂
S
∣
f
I
⋅
g
J
=\sum_{I\subseteq O}\sum_{J\subseteq O}\frac{1}{2^{size}}\sum_{T \subseteq O}(-1)^{|T\bigcap I|}(-1)^{|T\bigcap J|}(-1)^{|T\bigcap S|}\ f_I\cdot g_J
=I⊆O∑J⊆O∑2size1T⊆O∑(−1)∣T⋂I∣(−1)∣T⋂J∣(−1)∣T⋂S∣ fI⋅gJ
说明:这里能这样变换是因为 ( − 1 ) × ( − 1 ) = 1 (-1)\times (-1)=1 (−1)×(−1)=1对答案无影响,会消掉,所以如果 I ⨁ J ⨁ S I\bigoplus J\bigoplus S I⨁J⨁S有或者没有某个元素,和 T T T交后,会被如上方法抵消影响。
举个栗子: I , J , S I,J,S I,J,S的某个元素 { v } \{v\} {v}的有无情况如下(因为都与 T T T相交,所以可以不管 T T T的影响):
{ 1 , 0 , 0 } { 0 , 1 , 0 } { 0 , 0 , 1 } { 1 , 1 , 1 } \{1,0,0\} \{0,1,0\} \{0,0,1\} \{1,1,1\} {1,0,0}{0,1,0}{0,0,1}{1,1,1},此时 I ⨁ J ⨁ S I\bigoplus J\bigoplus S I⨁J⨁S会有该元素,但是三种情况分别为 ( − 1 ) 1 , ( − 1 ) 3 (-1)^1,(-1)^3 (−1)1,(−1)3都为 − 1 -1 −1不变,同样 I ⨁ J ⨁ S I\bigoplus J\bigoplus S I⨁J⨁S没有该元素的都为 1 1 1不变,所以可以拆开,其实因为$\bigoplus 出 来 有 该 元 素 必 须 有 奇 数 个 1 , 无 该 元 素 必 须 有 偶 数 个 1 , 所 以 出来有该元素必须有奇数个1,无该元素必须有偶数个1,所以 出来有该元素必须有奇数个1,无该元素必须有偶数个1,所以(-1) 的 奇 数 次 方 为 − 1 , 偶 数 次 方 为 0 , 而 奇 数 的奇数次方为-1,偶数次方为0,而奇数 的奇数次方为−1,偶数次方为0,而奇数\bigoplus 出 来 有 该 元 素 , 就 是 出来有该元素,就是 出来有该元素,就是(-1)1$还是$-1$,偶数没有,就是$(-1)0$,所以为1。
= 1 2 s i z e ∑ T ⊆ O ( − 1 ) ∣ T ⋂ S ∣ ( ∑ I ⊆ O ( − 1 ) ∣ T ⋂ I ∣ f I ) ( ∑ J ⊆ O ( − 1 ) ∣ T ⋂ J ∣ g J ) =\frac{1}{2^{size}}\sum_{T\subseteq O}(-1)^{|T\bigcap S|}\left(\sum_{I\subseteq O}(-1)^{|T\bigcap I|}f_I\right)\left(\sum_{J\subseteq O}(-1)^{|T\bigcap J|}g_J\right) =2size1T⊆O∑(−1)∣T⋂S∣(I⊆O∑(−1)∣T⋂I∣fI)(J⊆O∑(−1)∣T⋂J∣gJ)
这里前半部分 1 2 s i z e ∑ T ⊆ O ( − 1 ) ∣ T ⋂ S ∣ \frac{1}{2^{size}}\sum_{T\subseteq O}(-1)^{|T\bigcap S|} 2size1∑T⊆O(−1)∣T⋂S∣只有当 S = ∅ S=∅ S=∅时后半部分才为1,所以这个式子的值为1,然后对于单个集合 f f f的变换,我们可以将集合 g g g看作一个常量,也就是式子最后半部分关于 g g g的那个括号里的值可以看作1,所以这种形式可以启发我们定义以下变换。
这样我们就可以定义一种变换,对于一个集合幂级数
f
f
f,我们定义它的沃尔什变换为集合幂级数
f
∧
f^{\wedge}
f∧,其中:
f
S
∧
=
∑
T
⊆
O
f
T
(
−
1
)
∣
S
⋂
T
∣
f^{\wedge }_S=\sum_{T\subseteq O}f_T(-1)^{|S\bigcap T|}
fS∧=T⊆O∑fT(−1)∣S⋂T∣
其逆变换同样由上面推导式得到:
f
S
=
1
2
s
i
z
e
∑
T
⊆
O
f
T
∧
(
−
1
)
∣
S
⋂
T
∣
f_S=\frac{1}{2^{size}}\sum_{T\subseteq O}f^{\wedge}_T(-1)^{|S\bigcap T|}
fS=2size1T⊆O∑fT∧(−1)∣S⋂T∣
再从集合对称差卷积得到:
h
S
∧
=
f
S
∧
g
S
∧
h^{\wedge}_S=f^{\wedge}_S g^{\wedge}_S
hS∧=fS∧gS∧
于是我们先可以对 f , g f,g f,g做沃尔什变换,再相乘得到 h ∧ h^{\wedge} h∧然后对其做沃尔什逆变换得到答案 h h h
这里可以用递推的方法,我们设
f
S
∧
(
i
)
f^{\wedge (i)}_S
fS∧(i)为只考虑那些与
S
S
S的对称差是
{
1
,
…
,
i
}
\{1,\dots ,i\}
{1,…,i}的子集的沃尔什变换的第
S
S
S项(也就是
T
⨁
S
⊆
{
1
,
…
,
i
}
T\bigoplus S\subseteq \{1,\dots ,i\}
T⨁S⊆{1,…,i}的集合
T
T
T的
f
T
(
−
1
)
∣
S
⋂
T
∣
f_T(-1)^{|S\bigcap T|}
fT(−1)∣S⋂T∣之和),这里容易得到对于不包含
i
i
i的
S
S
S有
f
S
∧
(
i
)
=
f
S
∧
(
i
−
1
)
−
f
S
⋃
{
i
}
∧
(
i
−
1
)
f^{\wedge (i)}_S=f^{\wedge (i-1)}_S-f^{\wedge (i-1)}_{S\bigcup \{i\}}
fS∧(i)=fS∧(i−1)−fS⋃{i}∧(i−1)
f S ⋃ { i } ∧ ( i ) = f S ∧ ( i − 1 ) + f S ⋃ { i } ∧ ( i ) f^{\wedge (i)}_{S\bigcup \{i\}}=f^{\wedge (i-1)}_S+f^{\wedge (i)}_{S\bigcup \{i\}} fS⋃{i}∧(i)=fS∧(i−1)+fS⋃{i}∧(i)
说明:对于第一个式子,不含有 { i } \{i\} {i}元素的 f S ∧ ( i ) f^{\wedge (i)}_S fS∧(i)根据定义式 = ∑ T ⊆ O f T ( − 1 ) ∣ S ⋂ T ∣ =\sum_{T\subseteq O}f_T(-1)^{|S\bigcap T|} =∑T⊆OfT(−1)∣S⋂T∣,将其拆分为两部分 = ∑ T ⊆ O f T ( − 1 ) ∣ S ⋂ T ∣ [ { i } ∉ T ] + ∑ T ⊆ O f T ( − 1 ) ∣ S ⋂ T ∣ [ { i } ∈ T ] =\sum_{T\subseteq O}f_T(-1)^{|S\bigcap T|}[\{i\}\notin T]+\sum_{T\subseteq O}f_T(-1)^{|S\bigcap T|}[\{i\}\in T] =∑T⊆OfT(−1)∣S⋂T∣[{i}∈/T]+∑T⊆OfT(−1)∣S⋂T∣[{i}∈T],对于前半部分因为已经是 S S S不含有 i i i了,但是对于后者,我们要用 S ⋃ { i } S\bigcup \{i\} S⋃{i}来表示,所以当 S S S换成 S ⋃ { i } S\bigcup \{i\} S⋃{i}时, ∣ S ⋂ T ∣ |S\bigcap T| ∣S⋂T∣变成 ∣ ( S ⋃ { i } ) ⋂ T ∣ |(S\bigcup \{i\})\bigcap T| ∣(S⋃{i})⋂T∣,它的集合大小会增加1,因为开始 S S S中没有 { i } \{i\} {i},交出来也没有,后面 ∣ S ⋃ { i } ∣ |S\bigcup \{i\}| ∣S⋃{i}∣中有了,所以交出来就比原来多了一个 { i } \{i\} {i}(这里的 T T T中是有 { i } \{i\} {i}的),所以 ∑ T ⊆ O f T ( − 1 ) ∣ S ⋂ T ∣ [ { i } ∈ T ] = − ∑ T ⊆ O f T ( − 1 ) ∣ ( S ⋃ { i } ) ⋂ T ∣ [ { i } ∈ T ] \sum_{T\subseteq O}f_T(-1)^{|S\bigcap T|}[\{i\}\in T]=-\sum_{T\subseteq O}f_T(-1)^{|(S\bigcup \{i\})\bigcap T|}\ \ \ \ \ [\{i\}\in T] ∑T⊆OfT(−1)∣S⋂T∣[{i}∈T]=−∑T⊆OfT(−1)∣(S⋃{i})⋂T∣ [{i}∈T],所以可以得到一式。
对于二式,也是同样的道理,开始变成了含有 { i } \{i\} {i}的集合 S ⋃ { i } S\bigcup \{i\} S⋃{i},同样用定义式将其拆成两部分 ∑ T ⊆ O f T ( − 1 ) ∣ ( S ⋃ { i } ) ⋂ T ∣ [ { i } ∉ T ] + ∑ T ⊆ O f T ( − 1 ) ∣ ( S ⋃ { i } ) ⋂ T ∣ [ { i } ∈ T ] \sum_{T\subseteq O}f_T(-1)^{|(S\bigcup \{i\})\bigcap T|}\ \ \ \ \ [\{i\}\notin T]+\sum_{T\subseteq O}f_T(-1)^{|(S\bigcup \{i\})\bigcap T|}\ \ \ \ \ [\{i\}\in T] ∑T⊆OfT(−1)∣(S⋃{i})⋂T∣ [{i}∈/T]+∑T⊆OfT(−1)∣(S⋃{i})⋂T∣ [{i}∈T],然后后半部分已经为 S ⋃ { i } S\bigcup \{i\} S⋃{i}所以要将前半部分变成没有 { i } \{i\} {i}的 S S S,同样来看, ∣ ( S ⋃ { i } ) ⋂ T ∣ |(S\bigcup \{i\})\bigcap T| ∣(S⋃{i})⋂T∣和 ∣ S ⋂ T ∣ |S\bigcap T| ∣S⋂T∣没有变化,因为这里的 T T T是不包含 { i } \{i\} {i}元素的,所以可以得到二式。
但是对于最底层的 f S ∧ ( 0 ) f^{\wedge (0)}_S fS∧(0),根据定义来看,我们最底层的 T T T只能等于 S S S,这样 S ⨁ T = ∅ S\bigoplus T=∅ S⨁T=∅,所以 f S ∧ ( 0 ) f^{\wedge (0)}_S fS∧(0),当 S S S大小为奇数时它等于 − f S -f_S −fS,为偶数时他就等于 f S f_S fS,这样十分不好处理,所以我们统一规定, f S ∧ ( 0 ) = f S f^{\wedge (0)}_S=f_S fS∧(0)=fS,那么对于上面求出的式子也要进行改动。
我们这里假设
(
−
1
)
∣
S
⋂
T
∣
=
1
(-1)^{|S\bigcap T|}=1
(−1)∣S⋂T∣=1,那么
(
−
1
)
∣
(
S
⋃
{
i
}
)
⋂
T
∣
=
−
1
(-1)^{|(S\bigcup \{i\})\bigcap T|}=-1
(−1)∣(S⋃{i})⋂T∣=−1,(其实反过来也可以,虽然式子符号(正负)会变,最终的答案不会改变),因为后者会比前者多一个元素
{
i
}
\{i\}
{i},所以
f
S
⋃
{
i
}
∧
(
i
)
f^{\wedge (i)}_{S\bigcup \{i\}}
fS⋃{i}∧(i)本来要乘以
−
1
-1
−1,又因为我们上面的重定义,所以不能乘以
−
1
-1
−1,所以对于后者要取相反数,这里我们便得到了最终的集合对称差卷积的沃尔什变换的式子:
f
S
∧
(
i
)
=
f
S
∧
(
i
−
1
)
+
f
S
⋃
{
i
}
∧
(
i
−
1
)
f^{\wedge (i)}_S=f^{\wedge (i-1)}_S+f^{\wedge (i-1)}_{S\bigcup \{i\}}
fS∧(i)=fS∧(i−1)+fS⋃{i}∧(i−1)
f S ⋃ { i } ∧ ( i ) = f S ∧ ( i − 1 ) − f S ⋃ { i } ∧ ( i ) f^{\wedge (i)}_{S\bigcup \{i\}}=f^{\wedge (i-1)}_S-f^{\wedge (i)}_{S\bigcup \{i\}} fS⋃{i}∧(i)=fS∧(i−1)−fS⋃{i}∧(i)
其实也就和之前的证明方法的结果相同了, f ∧ = ( f − + f + , f − − f + ) f^{\wedge}=(f^-+f^+,f^--f^+) f∧=(f−+f+,f−−f+)。
逆变换也就是原来的变换乘以了 1 2 s i z e \frac{1}{2^{size}} 2size1,所以我们发现这里可以不用像原来的逆变换每次除以2,可以做顺变换,最后答案除以 2 s i z e 2^{size} 2size即可。
注:对于其它两个集合运算也有类似的证明,但是过于繁琐,而且实际应用中集合对称差用到此证明公式的最多,所以其余两个就不讲此类证法,有兴趣的读者可自行查阅资料。
到这里,沃尔什变换就大概完整了。
三种卷积的变换与逆变换
集合并 ⋃ \bigcup ⋃
-
沃尔什变换 f ∧ = ( f − , f − + f + ) f^{\wedge}=(f^-,f^-+f^+) f∧=(f−,f−+f+)
-
沃尔什逆变换 f = ( f ∧ − , f ∧ + − f ∧ − ) f=(f^{\wedge -},f^{\wedge +}-f^{\wedge -}) f=(f∧−,f∧+−f∧−)
集合交 ⋂ \bigcap ⋂
-
沃尔什变换 f ∧ = ( f − + f + , f + ) f^{\wedge}=(f^-+f^+,f^+) f∧=(f−+f+,f+)
-
沃尔什逆变换 f = ( f ∧ − − f ∧ + , f ∧ + ) f=(f^{\wedge -}-f^{\wedge +},f^{\wedge +}) f=(f∧−−f∧+,f∧+)
集合对称差 ⨁ \bigoplus ⨁
-
沃尔什变换 f ∧ = ( f − + f + , f − − f + ) f^{\wedge}=(f^-+f^+,f^--f^+) f∧=(f−+f+,f−−f+)
-
沃尔什逆变换 f = ( f ∧ − + f ∧ + 2 , f ∧ − − f ∧ + 2 ) f=(\frac{f^{\wedge -}+f^{\wedge +}}{2},\frac{f^{\wedge -}-f^{\wedge +}}{2}) f=(2f∧−+f∧+,2f∧−−f∧+)
注意:在取模运算时,除2要变成乘以2对应的逆元。
那么对于全集的每个元素,我们可以用0或者1来表示它是否在某个集合里,就可以用一个二进制数来表示集合,这在运算上就十分方便了,原因如下:
-
对于集合并,它就相当于位运算中的 ∣ ( o r ) |(or) ∣(or), 1011 ⋃ 0110 = 1111 1011\bigcup0110=1111 1011⋃0110=1111 , 1011 ∣ 0110 = 1111 1011|0110=1111 1011∣0110=1111。
-
对于集合交,它就相当于位运算中的&(and), 1011 ⋂ 0110 = 0010 1011\bigcap0110=0010 1011⋂0110=0010 , 1011&0110=0010。
-
对于集合对称差,你应该想到了,没错,它就是 ∧ ( x o r ) {\wedge} (xor) ∧(xor) , 1011 ⨁ 0110 = 1101 1011\bigoplus 0110 = 1101 1011⨁0110=1101 , 1011 ∧ 0110 = 1101 1011\wedge 0110 = 1101 1011∧0110=1101
那么由于位运算每一位之间互不影响,所以我们可以把它按照当前位为0或1分治,也就是表示当前元素有无分治。
但是递归虽然可以,有时常数以及实际效率并不是很优秀,所以我们考虑不用递归的形式。
我们发现它的全集 O O O,可以用二进制数全部表示出来,转换为十进制来看就是 { 0 , 1 , 2 , … , 2 s i z e − 1 } \{0,1,2,\dots ,2^{size}-1\} {0,1,2,…,2size−1},其中 0 0 0表示 ∅ ∅ ∅。
那么我们来看二进制的形式:
{ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 } \{0,1,2,3,4,5,6,7\} {0,1,2,3,4,5,6,7}就等于
{ 000 , 001 , 010 , 011 , 100 , 101 , 110 , 111 } \{000,001,010,011,100,101,110,111\} {000,001,010,011,100,101,110,111}
我们发现对于第 i i i位(从右往左数),它为 0 0 0相隔 2 i − 1 2^{i-1} 2i−1个数,为 1 1 1也是,如 1 : { 0 , 1 , 0 , 1 , 0 , 1 , 0 , 1 } 1:\{0,1,0,1,0,1,0,1\} 1:{0,1,0,1,0,1,0,1}, 2 : { 0 , 0 , 1 , 1 , 0 , 0 , 1 , 1 } 2:\{0,0,1,1,0,0,1,1\} 2:{0,0,1,1,0,0,1,1}, 3 : { 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 } 3:\{0,0,0,0,1,1,1,1\} 3:{0,0,0,0,1,1,1,1},以此类推,所以我们可以用循环来代替递归。
第一层循环,枚举当前为第 i i i位
for(int bit=1;bit<(1<<size);bit<<=1)
第二次枚举当前连续的一段0和1
int inc=bit<<1;
for(int i=0;i<(1<<size);i+=inc)
依次枚举这一段对应的0和1,
for(int j=0;j<bit;j++)
那么我们用 x x x表示0这一位, y y y表示1这一位,那么就有
x=a[i+j],y=a[i+j+bit];
//eg.当前这一位是{0,0,1,1,0,0,1,1}
// | |
// i i+bit
// j {0~1}
所以对于三种变化我们计算如下
运算方式 | or并 | and交 | xor对称差 |
---|---|---|---|
沃尔什变换 | a[i+j+bit]=x+y | a[i+j]=x+y | a[i+j]=x+y,a[i+j+bit]=x-y |
沃尔什逆变换 | a[i+j+bit]=y-x | a[i+j]=x-y | a[i+j]=(x+y)/2,a[i+j+bit]=(x-y)/2 |
那么代码就很容易写了。
模板题目
Luogu P4717
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int M=1<<19;
const ll mod=998244353ll;
int n;
ll A[M],B[M];
ll a[M],b[M];
ll c[3][M],inv;
ll pow(ll a){
ll b=mod-2,ans=1;
for(;b;b>>=1,a=(a*a)%mod){
if(b&1) ans=(ans*a)%mod;
}
return ans;
}
void WT(ll *w,int n,int f,int type){
ll x,y;
//type:0 or,1 and,2 xor
//f=1 沃尔什变换 f=-1 沃尔什逆变换
for(int d=1;d<n;d<<=1){
for(int ad=d<<1,i=0;i<n;i+=ad){
for(int j=0;j<d;j++){
x=w[i+j],y=w[i+j+d];
if(f==1){
switch(type){
case 0:{
w[i+j+d]=(x+y)%mod;
break;
}
case 1:{
w[i+j]=(x+y)%mod;
break;
}
case 2:{
w[i+j]=(x+y)%mod;
w[i+j+d]=(x-y+mod)%mod;
break;
}
}
}else{
switch(type){
case 0:{
w[i+j+d]=(y-x+mod)%mod;
break;
}
case 1:{
w[i+j]=(x-y+mod)%mod;
break;
}
case 2:{
w[i+j]=(x+y)*inv%mod;
w[i+j+d]=(((x-y)*inv%mod)+mod)%mod;
break;
}
}
}
}
}
}
}
void FWT(ll *a,ll *b,int n){
for(int f=0;f<3;f++){
memset(A,0,sizeof(A));memset(B,0,sizeof(B));
for(int i=0;i<n;i++) A[i]=a[i],B[i]=b[i];
WT(A,n,1,f);WT(B,n,1,f);
for(int i=0;i<n;i++) A[i]=(A[i]*B[i])%mod;
WT(A,n,-1,f);
for(int i=0;i<n;i++) c[f][i]=(A[i]%mod+mod)%mod;
}
}
int main(){
inv=pow(2);
scanf("%d",&n);n=(1<<n);
for(int i=0;i<n;i++) scanf("%lld",&a[i]);
for(int i=0;i<n;i++) scanf("%lld",&b[i]);
FWT(a,b,n);
for(int i=0;i<3;i++){
for(int j=0;j<n;j++) printf("%lld ",c[i][j]);puts("");
}
return 0;
}
hdxrie的更简洁的模板IN THERE
其中有一些简单的特判,避免了我的switch语句的麻烦。(感谢hdxrie提供)
而暴力计算其实十分简单,为了方便理解,这里放一份暴力代码。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int M=1<<19;
const ll mod=998244353ll;
int n;
ll a[M],b[M];
ll cxor[M],cor[M],cand[M];
int main(){
scanf("%d",&n);
if(n>13) return 0;
n=(1<<n);
for(int i=0;i<n;i++)scanf("%lld",&a[i]);
for(int i=0;i<n;i++)scanf("%lld",&b[i]);
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
ll now=(a[i]*b[j])%mod;
(cor[i|j]+=now)%=mod;
(cxor[i^j]+=now)%=mod;
(cand[i&j]+=now)%=mod;
}
}
for(int i=0;i<n;i++) printf("%lld ",cor[i]);puts("");
for(int i=0;i<n;i++) printf("%lld ",cand[i]);puts("");
for(int i=0;i<n;i++) printf("%lld ",cxor[i]);puts("");
return 0;
}
#End
这里就结束了,如果有不清楚或者错误的地方,欢迎向我提出,我会尽量及时回答与修改~~(我也是个初学者)~~。
参考
具体应用与题目
参考这篇文章
#Thanks♪(・ω・)ノ for reading!