前言
对于一个序列,将其中的元素一一映射到一个多项式函数的系数上,这个多项式函数便叫做该序列的生成函数。
对于序列
f
0
,
f
1
,
⋯
,
f
n
f_0,f_1,\cdots,f_n
f0,f1,⋯,fn,
f
(
x
)
=
∑
i
=
0
n
f
i
x
i
f(x)= \sum_{i=0}^n f_ix^i
f(x)=∑i=0nfixi为其生成函数,我们一般叫他们多项式。
卷积是通过两个函数
f
f
f和
g
g
g生成第三个函数的一种数学运算,其本质是一种特殊的积分变换。
当多项式卷积为
C
k
=
∑
i
+
j
=
k
A
i
×
B
j
C_k=\sum_{i+j=k} A_i \times B_j
Ck=∑i+j=kAi×Bj。
我们可以用FFT,NTT来做。
而当多项式卷积为(这里
⊕
\oplus
⊕指代异或):
C
k
=
∑
i
∣
j
=
k
A
i
×
B
j
C_k=\sum_{i|j=k} A_i \times B_j
Ck=i∣j=k∑Ai×Bj
C
k
=
∑
i
&
j
=
k
A
i
×
B
j
C_k=\sum_{i\&j=k} A_i \times B_j
Ck=i&j=k∑Ai×Bj
C
k
=
∑
i
⊕
j
=
k
A
i
×
B
j
C_k=\sum_{i \oplus j=k} A_i \times B_j
Ck=i⊕j=k∑Ai×Bj我们的FFT,NTT就不能派上用场了,这就要用到下面讲的FWT\FMT。而一般来讲,FMT(快速莫比乌斯变换)处理OR,AND问题,而FWT(快速沃尔什变换)是处理XOR问题的。
正文
OR运算的FMT
我们不妨先回忆一下FFT的思路:
先对于一个多项式求出它在带入若干单位根后的点值表示法,然后把点值表示法下的多项式乘起来,然后再把点值表示法变成多项式表示法。
FMT的思路与此类似。
不过,由于这三类与位运算关联的多项式卷积有其特殊的性质,我们不再代入若干指定的值,而是先进行一些变换。
我们不妨进行如下定义:
要求
c
k
=
∑
i
∣
j
=
k
a
i
×
b
j
c_k=\sum_{i|j=k} a_i \times b_j
ck=i∣j=k∑ai×bj设
F
M
T
(
f
n
)
=
∑
i
∣
n
=
n
f
i
FMT(f_n)= \sum_{i | n=n} f_i
FMT(fn)=i∣n=n∑fi不难发现
F
M
T
(
a
t
)
×
F
M
T
(
b
t
)
=
(
∑
i
∣
t
=
t
a
i
)
×
(
∑
j
∣
t
=
t
b
j
)
=
∑
i
∣
t
=
t
∑
j
∣
t
=
t
(
a
i
×
b
j
)
=
∑
(
i
∣
j
)
∣
t
=
t
(
a
i
×
b
j
)
=
F
M
T
(
c
t
)
FMT(a_t) \times FMT(b_t) \\ =(\sum_{i|t=t}a_i) \times (\sum_{j|t=t}b_j) \\ = \sum_{i|t=t} \sum_{j|t=t} (a_i \times b_j) \\ = \sum_{(i|j)|t=t}(a_i \times b_j) \\ =FMT(c_t)
FMT(at)×FMT(bt)=(∑i∣t=tai)×(∑j∣t=tbj)=∑i∣t=t∑j∣t=t(ai×bj)=∑(i∣j)∣t=t(ai×bj)=FMT(ct)
由此可得
F
M
T
(
a
t
)
×
F
M
T
(
b
t
)
=
F
M
T
(
c
t
)
FMT(a_t) \times FMT(b_t)=FMT(c_t)
FMT(at)×FMT(bt)=FMT(ct),这虽然不是真正的点值表示法,却在多项式乘法中发挥着同样的作用,我们只要找到快速算出
F
M
T
(
f
n
)
FMT(f_n)
FMT(fn)和逆运算
F
M
T
(
f
n
)
FMT(f_n)
FMT(fn)的方法即可。
我们先拿出以前FFT用过的经典三重循环,看看能不能得到什么启示。
for ( int i = 2 ; i <= len ; i <<= 1 ) {//枚举步长,从递归的下面往上走
for ( int j = 0 ; j <= len - 1 ; j += i ) {//走一遍步长
for ( int k = j ; k < j + i / 2 ; ++k ) {//枚举每块区间内的每一个元素
int u = f[k];
int v = f[k + i / 2];
}
}
}
通过观察不难发现,假设在
i
i
i的某一次取值中
2
t
=
i
(
k
⩾
1
)
2^t=i(k\geqslant1)
2t=i(k⩾1),则
j
j
j的那层循环只改变
u
u
u和
v
v
v下标数
k
k
k和
k
+
i
2
k+\frac{i}{2}
k+2i的二进制表示中第
t
t
t位以及后面的数,而
k
k
k的那层循环开始的数为
i
i
i的倍数,二进制表示下后面显然有至少
t
t
t个零,而
k
k
k最多循环出
2
t
−
1
2^{t-1}
2t−1次,还有一次是后面至少
t
t
t个零的数,所以这层循环刚好不影响到第
t
−
1
t-1
t−1位,因而这式子中
k
∣
(
k
+
i
2
)
=
k
+
i
2
k|(k+\frac{i}{2})=k+\frac{i}{2}
k∣(k+2i)=k+2i且
k
+
i
2
k+\frac{i}{2}
k+2i刚好比
k
k
k多出个
2
t
−
1
2^{t-1}
2t−1。通过背包思想不难求出FMT。
至于逆运算?通过子集和反推,只要把加的减回去就可以了(至于正确性的证明?我不会QWQ )。
代码如下:
void FMTor( int f[] , int len , int on ) {//on=1\-1切换模式
for ( int i = 2 ; i <= len ; i <<= 1 ) {//枚举步长,从递归的下面往上走
for ( int j = 0 ; j <= len - 1 ; j += i ) {//走一遍步长
for ( int k = j ; k < j + i / 2 ; ++k ) {//枚举每块区间内的每一个元素
f[k + i / 2] += f[k] * on;
}
}
}
return;
}
时间复杂度和FFT一样,为
Θ
(
n
log
n
)
\varTheta(n\log n)
Θ(nlogn)。
整体思路:
a
i
→
F
M
T
(
a
i
)
b
i
→
F
M
T
(
b
i
)
F
M
T
(
c
i
)
=
F
M
T
(
a
i
)
×
F
M
T
(
b
i
)
F
M
T
(
c
i
)
→
c
i
a_i \to FMT(a_i) \\ b_i \to FMT(b_i) \\ FMT(c_i)=FMT(a_i) \times FMT(b_i) \\ FMT(c_i) \to c_i
ai→FMT(ai)bi→FMT(bi)FMT(ci)=FMT(ai)×FMT(bi)FMT(ci)→ci
AND运算的FMT
和OR很多地方相似。
要求
c
k
=
∑
i
&
j
=
k
a
i
×
b
j
c_k=\sum_{i \& j=k} a_i \times b_j
ck=i&j=k∑ai×bj设
F
M
T
(
f
n
)
=
∑
i
&
n
=
n
f
i
FMT(f_n)= \sum_{i \& n=n} f_i
FMT(fn)=i&n=n∑fi不难发现
F
M
T
(
a
t
)
×
F
M
T
(
b
t
)
=
(
∑
i
&
t
=
t
a
i
)
×
(
∑
j
&
t
=
t
b
j
)
=
∑
i
&
t
=
t
∑
j
&
t
=
t
(
a
i
×
b
j
)
=
∑
(
i
&
j
)
&
t
=
t
(
a
i
×
b
j
)
=
F
M
T
(
c
t
)
FMT(a_t) \times FMT(b_t) \\ =(\sum_{i \& t=t}a_i) \times (\sum_{j \& t=t}b_j) \\ = \sum_{i \& t=t} \sum_{j \& t=t} (a_i \times b_j) \\ = \sum_{(i \& j) \& t=t}(a_i \times b_j) \\ =FMT(c_t)
FMT(at)×FMT(bt)=(∑i&t=tai)×(∑j&t=tbj)=∑i&t=t∑j&t=t(ai×bj)=∑(i&j)&t=t(ai×bj)=FMT(ct)
易得
i
∣
j
=
i
i|j=i
i∣j=i时
i
i
i和
j
j
j满足
i
&
j
=
j
i \&j=j
i&j=j。加给
k
+
i
2
k+\frac{i}{2}
k+2i变成加给
k
k
k,所以我们只要把上面的代码微改即可。
代码如下:
void FMTand( int f[] , int len , int on ) {
for ( int i = 2 ; i <= len ; i <<= 1 ) {//枚举步长,从递归的下面往上走
for ( int j = 0 ; j <= len - 1 ; j += i ) {//走一遍步长
for ( int k = j ; k < j + i / 2 ; ++k ) {//枚举每块区间内的每一个元素
f[k] += f[k + i / 2] * on;
}
}
}
return;
}
XOR运算的FMT
xor运算的变换不好想到。我们先设
p
o
p
c
o
u
n
t
(
x
)
popcount(x)
popcount(x)为
x
x
x的二进制表示下
1
1
1的个数。
不难发现异或运算满足
p
o
p
c
o
u
n
t
(
a
)
+
p
o
p
c
o
u
n
t
(
b
)
≡
p
o
p
c
o
u
n
t
(
a
⊕
b
)
(
m
o
d
2
)
popcount(a)+popcount(b) \equiv popcount(a \oplus b) (\mod 2)
popcount(a)+popcount(b)≡popcount(a⊕b)(mod2)。
我们设
F
W
T
(
f
t
)
=
∑
i
=
0
n
(
−
1
)
∣
i
&
t
∣
f
i
FWT(f_t)=\sum_{i=0}^n(-1)^{|i \& t|}f_i
FWT(ft)=∑i=0n(−1)∣i&t∣fi,其中设
∣
x
∣
|x|
∣x∣表示
p
o
p
c
o
u
n
t
(
x
)
popcount(x)
popcount(x)。
不难发现
F
W
T
(
a
t
)
×
F
W
T
(
b
t
)
=
(
∑
i
=
0
n
(
−
1
)
∣
i
&
t
∣
a
i
)
×
(
∑
j
=
0
n
(
−
1
)
∣
j
&
t
∣
b
j
)
=
∑
i
=
0
n
∑
j
=
0
n
(
−
1
)
∣
i
&
t
∣
(
−
1
)
∣
j
&
t
∣
a
i
b
j
=
∑
i
=
0
n
∑
j
=
0
n
(
−
1
)
∣
i
&
t
∣
+
∣
j
&
t
∣
a
i
b
j
FWT(a_t) \times FWT(b_t) \\ =(\sum_{i=0}^n(-1)^{|i \& t|}a_i ) \times (\sum_{j=0}^n(-1)^{|j \& t|}b_j ) \\ = \sum_{i=0}^n\sum_{j=0}^n (-1)^{|i \& t|}(-1)^{|j \& t|}a_i b_j \\ = \sum_{i=0}^n\sum_{j=0}^n (-1)^{|i \& t| + |j \& t|}a_i b_j
FWT(at)×FWT(bt)=(∑i=0n(−1)∣i&t∣ai)×(∑j=0n(−1)∣j&t∣bj)=∑i=0n∑j=0n(−1)∣i&t∣(−1)∣j&t∣aibj=∑i=0n∑j=0n(−1)∣i&t∣+∣j&t∣aibj
由于异或满足相同为0不同为1,所以异或中两两消掉的1不改变
−
1
-1
−1的指数的奇偶性。即
∣
i
&
t
∣
+
∣
j
&
t
∣
=
∣
(
i
⊕
j
)
&
t
∣
|i \& t| + |j \& t|=|(i \oplus j) \& t|
∣i&t∣+∣j&t∣=∣(i⊕j)&t∣。
=
∑
i
=
0
n
∑
j
=
0
n
(
−
1
)
∣
(
i
⊕
j
)
&
t
∣
a
i
b
j
=
F
W
T
(
c
t
)
= \sum_{i=0}^n\sum_{j=0}^n (-1)^{|(i \oplus j) \& t|}a_i b_j \\ =FWT(c_t)
=∑i=0n∑j=0n(−1)∣(i⊕j)&t∣aibj=FWT(ct)
FWT的计算要考虑分治,设
C
0
=
(
c
0
,
c
1
,
⋯
,
c
n
2
−
1
)
C_0=(c_0,c_1,\cdots,c_{\frac{n}{2}-1})
C0=(c0,c1,⋯,c2n−1),
C
1
=
(
c
n
2
,
c
n
2
+
1
,
⋯
,
c
n
−
1
)
C_1=(c_{\frac{n}{2}},c_{\frac{n}{2} + 1},\cdots,c_{n- 1})
C1=(c2n,c2n+1,⋯,cn−1)。
其中
F
M
T
(
C
0
)
,
F
M
T
(
C
1
)
FMT(C_0),FMT(C_1)
FMT(C0),FMT(C1)表示迭代过后能代表这些项的
F
M
T
FMT
FMT之和的项。
令
k
<
n
2
k \lt \frac{n}{2}
k<2n,有
F
W
T
(
c
k
)
=
∑
i
=
0
n
2
−
1
(
−
1
)
∣
i
&
k
∣
c
i
+
∑
i
=
0
n
2
−
1
(
−
1
)
∣
i
&
k
∣
c
i
+
n
2
=
F
M
T
(
C
0
)
+
F
M
T
(
C
1
)
FWT(c_k)=\sum_{i=0}^{\frac{n}{2}-1}(-1)^{|i \& k|}c_i + \sum_{i=0}^{\frac{n}{2}-1}(-1)^{|i \& k|}c_{i+\frac{n}{2}}=FMT(C_0)+FMT(C_1)
FWT(ck)=i=0∑2n−1(−1)∣i&k∣ci+i=0∑2n−1(−1)∣i&k∣ci+2n=FMT(C0)+FMT(C1)
F
W
T
(
c
k
+
n
2
)
=
∑
i
=
0
n
2
−
1
(
−
1
)
∣
i
&
k
∣
c
i
−
∑
i
=
0
n
2
−
1
(
−
1
)
∣
i
&
k
∣
c
i
+
n
2
=
F
M
T
(
C
0
)
−
F
M
T
(
C
1
)
FWT(c_k + \frac{n}{2})=\sum_{i=0}^{\frac{n}{2}-1}(-1)^{|i \& k|}c_i - \sum_{i=0}^{\frac{n}{2}-1}(-1)^{|i \& k|}c_{i+\frac{n}{2}}=FMT(C_0)-FMT(C_1)
FWT(ck+2n)=i=0∑2n−1(−1)∣i&k∣ci−i=0∑2n−1(−1)∣i&k∣ci+2n=FMT(C0)−FMT(C1)
这里有负一是因为强行给
−
1
-1
−1降了一次幂。
可得代码:
void FWTxor( int f[] , int len ) {
for ( int i = 2 ; i <= len ; i <<= 1 ) {//枚举步长,从递归的下面往上走
for ( int j = 0 ; j <= len - 1 ; j += i ) {//走一遍步长
for ( int k = j ; k < j + i / 2 ; ++k ) {//枚举每块区间内的每一个元素
int u = f[k];
int v = f[k + i / 2];
f[k] = u + v;
f[k + i / 2] = u - v;
}
}
}
return;
}
逆运算也不难推出
x
=
u
+
v
,
y
=
u
−
v
→
u
=
x
+
y
2
,
v
=
x
−
y
2
x=u+v,y=u-v \to u = \frac{x + y}{2},v = \frac{x - y}{2}
x=u+v,y=u−v→u=2x+y,v=2x−y。
结合起来:
void FWTxor( int f[] , int len , int on ) {
for ( int i = 2 ; i <= len ; i <<= 1 ) {//枚举步长,从递归的下面往上走
for ( int j = 0 ; j <= len - 1 ; j += i ) {//走一遍步长
for ( int k = j ; k < j + i / 2 ; ++k ) {//枚举每块区间内的每一个元素
int u = f[k];
int v = f[k + i / 2];
f[k] = u + v;
f[k + i / 2] = u - v;
if( on == -1 ) {
f[k] /= 2;
f[k + i / 2] /= 2;//一般是题目给模数然后乘逆元
}
}
}
}
return;
}
三个综合起来:
模板题
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define ll long long
using namespace std;
const int mod = 998244353;
int n;
ll inv2;
const int N = 1 << 17 | 1;
inline ll qw( ll a , ll b ) {
ll ans = 1;
while ( b ) {
if( b & 1 ) {
ans = ( ans * a ) % mod;
}
a = ( a * a ) % mod;
b >>= 1;
}
return ans;
}
void FMTor( ll f[] , int len , int on ) {//on=1\-1切换模式
for ( int i = 2 ; i <= len ; i <<= 1 ) {//枚举步长,从递归的下面往上走
for ( int j = 0 ; j <= len - 1 ; j += i ) {//走一遍步长
for ( int k = j ; k < j + i / 2 ; ++k ) {//枚举每块区间内的每一个元素
f[k + i / 2] =( ( f[k + i / 2] + f[k] * on ) % mod + mod ) % mod;
}
}
}
return;
}
void FMTand( ll f[] , int len , int on ) {
for ( int i = 2 ; i <= len ; i <<= 1 ) {//枚举步长,从递归的下面往上走
for ( int j = 0 ; j <= len - 1 ; j += i ) {//走一遍步长
for ( int k = j ; k < j + i / 2 ; ++k ) {//枚举每块区间内的每一个元素
f[k] =( ( f[k] + f[k + i / 2] * on ) % mod + mod ) % mod;
}
}
}
return;
}
void FWTxor( ll f[] , int len , int on ) {
for ( int i = 2 ; i <= len ; i <<= 1 ) {//枚举步长,从递归的下面往上走
for ( int j = 0 ; j <= len - 1 ; j += i ) {//走一遍步长
for ( int k = j ; k < j + i / 2 ; ++k ) {//枚举每块区间内的每一个元素
ll u = f[k];
ll v = f[k + i / 2];
f[k] = ( ( u + v ) % mod + mod ) % mod;
f[k + i / 2] = ( ( u - v ) % mod + mod ) % mod;
if( on == -1 ) {
f[k] = ( ( 1ll * f[k] * inv2 ) % mod + mod ) % mod;
f[k + i / 2] = ( ( 1ll * f[k + i / 2] * inv2 ) % mod + mod ) % mod;//一般是题目给模数然后乘逆元
}
}
}
}
return;
}
ll a[N] , b[N] , ta[N] , tb[N];
int main() {
inv2 = qw( 2ll , mod - 2 );
scanf("%d",&n);
int len = qw( 2 , n );
for ( int i = 0 ; i < len ; ++i ) {
scanf("%lld",&a[i]);
}
for ( int i = 0 ; i < len ; ++i ) {
scanf("%lld",&b[i]);
}
for ( int i = 0 ; i < len ; ++i ) {
ta[i] = a[i];
}
for ( int i = 0 ; i < len ; ++i ) {
tb[i] = b[i];
}
FMTor( a , len , 1 );
FMTor( b , len , 1 );
for ( int i = 0 ; i < len ; ++i ) {
a[i] = ( ( a[i] * b[i] ) % mod + mod ) % mod;
}
FMTor( a , len , -1 );
for ( int i = 0 ; i < len ; ++i ) {
printf("%lld ",(a[i] % mod + mod) % mod);
}
puts("");
for ( int i = 0 ; i < len ; ++i ) {
a[i] = ta[i];
}
for ( int i = 0 ; i < len ; ++i ) {
b[i] = tb[i];
}
FMTand( a , len , 1 );
FMTand( b , len , 1 );
for ( int i = 0 ; i < len ; ++i ) {
a[i] = ( ( a[i] * b[i] ) % mod + mod ) % mod;
}
FMTand( a , len , -1 );
for ( int i = 0 ; i < len ; ++i ) {
printf("%lld ",(a[i] % mod + mod) % mod);
}
puts("");
for ( int i = 0 ; i < len ; ++i ) {
a[i] = ta[i];
}
for ( int i = 0 ; i < len ; ++i ) {
b[i] = tb[i];
}
FWTxor( a , len , 1 );
FWTxor( b , len , 1 );
for ( int i = 0 ; i < len ; ++i ) {
a[i] = ( ( a[i] * b[i] ) % mod + mod ) % mod;
}
FWTxor( a , len , -1 );
for ( int i = 0 ; i < len ; ++i ) {
printf("%lld ",(a[i] % mod + mod) % mod);
}
puts("");
return 0;
}