FWT抄袭笔记

看到坐在我左边的 dalao 在学 FWT。

想着二轮前学一些很酷很炫很失败的算法,于是来背一下学一下吧。

抄袭来源

引入

我们利用 FFT 可以在 $O(n\log n)$ 的时间复杂度内计算

假如说我们把这个式子变一变

其中 $\vee$ 指按位或,$\wedge$ 指按位与,$\oplus$ 指按位异或。

我们还是希望能够在 $O(n\log n)$ 的时间复杂度内计算出答案。

这种时候我们就需要 FWT(快速沃尔什变换,Fast Walsh–Hadamard Transformation)了。

在 FFT 中,我们先计算出了 $F$ 和 $G$ 的点值多项式,然后将对应位直接相乘得到了 $F\times G$ 的点值多项式,最后还原回了 $F\times G$。

类似地,我们可不可以试着计算出两个多项式 $F^{\prime},G^{\prime}$,使得

当然可以啊不然还怎么会有 FWT(

一些约定

对于两个向量 $A$ 和 $B$,我们令

设 $F$ 的项数为 $2^{t}$。为了方便,我们将其看做一个 $2^{t}$ 维向量

然后我们定义

正变换

按位或

定义

我们发现

$t=0$ 时这十分显然,我们来考虑 $t\gt 0$ 时的情况。

首先很明显的一点是 $j\vee i=i\Leftrightarrow j\subseteq i$,也就是说 $j$ 是 $i$ 的子集。

对于 $F_{1}$ 中的一个下标 $i$,我们在 $F_{1}$ 左边接上 $F_{0}$ 后,这个 $i$ 就多了一个 $2^{t-1}$。

之前 $i$ 的每一个子集 $j$,也同样多了一个 $2^{t-1}$,它们产生的贡献已经被统计在了 $F_{1}^{\prime}[i]$ 内。

那么去掉这个 $2^{t-1}$,我们还有 $j\subseteq i+2^{t-1}$,这部分其实就是 $F_{0}^{\prime}[i]$ 的值。

据此,我们就有

按位与

定义

它具有类似的性质

证明略。

按位异或

定义

然后我们发现按位异或并没有像上面一样的和子集相关的性质。因此我们需要通过一些其他方式来证明它的正确性。

首先

如果 $F$ 和 $G$ 都只有一项,这十分显然。

否则

这个不会证,告辞。

逆变换

就是把刚刚算出来的 $F^{\prime}$ 变回 $F$。

啥意思啊看不懂啊(

然后您就可以切掉这道板子题 了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#include<cstdio>
#define re register
#define maxn 17
#define mod 998244353

namespace cltstream{
#define size 1048576
char cltin[size+1],*ih=cltin,*it=cltin;
inline char gc(){
#ifdef ONLINE_JUDGE
if(ih==it){
it=(ih=cltin)+fread(cltin,1,size,stdin);
if(ih==it)
return EOF;
}
return *ih++;
#else
return getchar();
#endif
}

char cltout[size+1],*oh=cltout,*ot=cltout+size;
inline void pc(char c){
if(oh==ot){
fwrite(cltout,1,size,stdout);
oh=cltout;
}
*oh++=c;
}
#define clop() fwrite(cltstream::cltout,1,cltstream::oh-cltstream::cltout,stdout),cltstream::oh=cltstream::cltout
#undef size

template <typename _tp>
inline void read(_tp& x){
int sn=1;
char c=gc();
for(;c!=45&&(c<48||c>57)&&c!=EOF;c=gc());
if(c==45&&c!=EOF)
sn=-1,c=gc();
for(x=0;c>=48&&c<=57&&c!=EOF;x=(x<<3)+(x<<1)+(c^48),c=gc());
x*=sn;
}

template <typename _tp>
inline void write(_tp x,char text=-1){
if(x<0)
pc(45),x=-x;
if(!x)
pc(48);
else{
int digit[22];
for(digit[0]=0;x;digit[++digit[0]]=x%10,x/=10);
for(;digit[0];pc(digit[digit[0]--]^48));
}
if(text>=0)
pc(text);
}
}

int n;
int F[1<<maxn][3],G[1<<maxn][3];

#define FWT(F,tp) {\
for(re int p=1;p<n;p<<=1)\
for(re int i=0;i<n;i+=p<<1)\
for(re int j=i;j<i+p;++j){\
F[j+p][0]=!tp?(F[j+p][0]+F[j][0])%mod:(F[j+p][0]-F[j][0]+mod)%mod;\
F[j][1]=!tp?(F[j][1]+F[j+p][1])%mod:(F[j][1]-F[j+p][1]+mod)%mod;\
re int x=F[j][2],y=F[j+p][2];\
F[j][2]=(x+y)%mod;\
F[j+p][2]=(x-y+mod)%mod;\
if(tp){\
F[j][2]=499122177LL*F[j][2]%mod;\
F[j+p][2]=499122177LL*F[j+p][2]%mod;\
}\
}\
}

int main(){
cltstream::read(n);
n=1<<n;
for(re int i=0;i<n;++i){
cltstream::read(F[i][0]);
F[i][1]=F[i][2]=F[i][0];
}
for(re int i=0;i<n;++i){
cltstream::read(G[i][0]);
G[i][1]=G[i][2]=G[i][0];
}
FWT(F,0);
FWT(G,0);
for(re int j=0;j<3;++j)
for(re int i=0;i<n;++i)
F[i][j]=1LL*F[i][j]*G[i][j]%mod;
FWT(F,1);
for(re int j=0;j<3;++j)
for(re int i=0;i<n;++i)
cltstream::write(F[i][j],i<n-1?32:10);
clop();
return 0;
}

单位元

我们知道 FFT 中的单位元多项式是 $F(x)=1$,类似地,FWT 是否也拥有这样的一个单位元呢?

当然是有的。

对于按位或和按位异或来说,它们的单位元是

对于按位与来说,它的单位元是

求逆

既然有了单位元,我们就不禁会想……这东西能求逆吗?

大概……能吧。

以下所有运算在模 $998244353$ 意义下进行。

按位或

给定一个多项式 $F$,让你求出一个多项式 $G$,使得

首先我们需要知道

注意到左半部分和右半部分的下标有一个重要区别就是从右往左第 $t$ 位是否为 $1$。

如果某个下标和右半部分的某个下标进行了按位或,它的第 $t$ 位一定是 $1$,那么它就跑到右半部分去了。

否则,即两个左半部分的下标进行了按位或,这种情况结果还是在左半部分。

然后我们就可以往下推了

边界条件 $G[0]=F[0]^{-1}$。

大致代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void Inv(re int* F,re int* G,re int n){
if(n==1)
G[0]=cltpow(F[0],mod-2);
else{
int tmp1[1<<maxn],tmp2[1<<maxn];
Inv(F,G,n>>1);
for(re int i=0;i<(n>>1);++i)
tmp1[i]=F[i]+F[(n>>1)+i];
Inv(tmp1,tmp2,n>>1);
FWT(F+(n>>1),n>>1,0);
FWT(G,n>>1,0);
FWT(tmp2,n>>1,0);
for(re int i=0;i<(n>>1);++i)
tmp2[i]=1LL*F[(n>>1)+i]*G[i]%mod*tmp2[i]%mod;
FWT(F+(n>>1),n>>1,1);
FWT(G,n>>1,1);
FWT(tmp2,n>>1,1);
for(re int i=0;i<(n>>1);++i)
G[(n>>1)+i]=(mod-tmp2[i])%mod;
}
}

时间复杂度非常显然是 $O(n^{2}2^{n})$。

按位与

给定一个多项式 $F$,让你求出一个多项式 $G$,使得

我们可以用类似的过程得到如下结论

按位异或

给定一个多项式 $F$,让你求出一个多项式 $G$,使得

首先我们有

也就是说

将上面两个式子相加 、 相减,得到

于是

时间复杂度 $O(n2^{n})$。

这就没了?并不是。

如果您按照上面的式子写好程序,然后往里面输入

1
2
2
1 2 3 4

这样一组数据,您会发现您的程序给出的结果并不正确。

那么原因是什么呢?是我们式子推错了吗?

输出一下中间的运算过程,我们发现,在中间有一步,我们计算 $(2,2)^{-1}$ 时,需要用到 $(0)^{-1}$。

显然它不存在,但是同样显然的是,如果 $(2,2)^{-1}$ 存在,那么这个过程不应该出现这样的问题。

所以说原因就是,$(2,2)^{-1}$,或者更进一步地,$(1,2,3,4)^{-1}$不存在

子集卷积

让我来看看有没有点进去看完题面一脸懵逼地关闭标签页的。

这道题是让我们求这个东西

这比我们之前看到的卷积拥有更加严格的要求,$S\wedge T=\varnothing$,很明显,这是在提示我们抄题解。

我们定义

然后我们从小到大枚举 $i$,计算

为了理解这个式子,我们先将其展开

根据定义,$F_{i,S}$ 和 $G_{i,S}$ 有值当且仅当 $|S|=i$,因此这就相当于

因为 $S$ 和 $T$ 可能会有交集,从而 $i\gt |R|$ 时 $H_{i,R}$ 也可能有值,这是不符合定义的,因此(如果有必要的话)我们需要手动清空。

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include<cstdio>
#define re register
#define maxn 20
#define mod 1000000009

namespace cltstream{
#define size 1048576
char cltin[size+1],*ih=cltin,*it=cltin;
inline char gc(){
#ifdef ONLINE_JUDGE
if(ih==it){
it=(ih=cltin)+fread(cltin,1,size,stdin);
if(ih==it)
return EOF;
}
return *ih++;
#else
return getchar();
#endif
}

char cltout[size+1],*oh=cltout,*ot=cltout+size;
inline void pc(char c){
if(oh==ot){
fwrite(cltout,1,size,stdout);
oh=cltout;
}
*oh++=c;
}
#define clop() fwrite(cltstream::cltout,1,cltstream::oh-cltstream::cltout,stdout),cltstream::oh=cltstream::cltout
#undef size

template <typename _tp>
inline void read(_tp& x){
int sn=1;
char c=gc();
for(;c!=45&&(c<48||c>57)&&c!=EOF;c=gc());
if(c==45&&c!=EOF)
sn=-1,c=gc();
for(x=0;c>=48&&c<=57&&c!=EOF;x=(x<<3)+(x<<1)+(c^48),c=gc());
x*=sn;
}

template <typename _tp>
inline void write(_tp x,char text=-1){
if(x<0)
pc(45),x=-x;
if(!x)
pc(48);
else{
int digit[22];
for(digit[0]=0;x;digit[++digit[0]]=x%10,x/=10);
for(;digit[0];pc(digit[digit[0]--]^48));
}
if(text>=0)
pc(text);
}
}

int n,N;
int cnt[1<<maxn],F[maxn+1][1<<maxn],G[maxn+1][1<<maxn],H[maxn+1][1<<maxn];

inline void FWT(re int* F,re int tp){
for(re int p=1;p<N;p<<=1)
for(re int i=0;i<N;i+=p<<1)
for(re int j=i;j<i+p;++j)
F[j+p]=!tp?(F[j+p]+F[j])%mod:(F[j+p]-F[j]+mod)%mod;
}

int main(){
cltstream::read(n);
N=1<<n;
for(re int i=1;i<N;++i)
cnt[i]=cnt[i>>1]+(i&1);
for(re int i=0;i<N;++i)
cltstream::read(F[cnt[i]][i]);
for(re int i=0;i<N;++i)
cltstream::read(G[cnt[i]][i]);
for(re int i=0;i<=n;++i){
FWT(F[i],0);
FWT(G[i],0);
for(re int j=0;j<=i;++j)
for(re int S=0;S<N;++S)
H[i][S]=(H[i][S]+1LL*F[j][S]*G[i-j][S]%mod)%mod;
FWT(H[i],1);
for(re int S=0;S<N;++S)
cnt[S]==i||(H[i][S]=0);
}
for(re int i=0;i<N;++i)
cltstream::write(H[cnt[i]][i],32);
clop();
return 0;
}

例题

评论