看到坐在我左边的 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 ]=499122177L L*F[j][2 ]%mod;\ F[j+p][2 ]=499122177L L*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]=1L L*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]=1L L*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})$。
这就没了?并不是。
如果您按照上面的式子写好程序,然后往里面输入
这样一组数据,您会发现您的程序给出的结果并不正确。
那么原因是什么呢?是我们式子推错了吗?
输出一下中间的运算过程,我们发现,在中间有一步,我们计算 $(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]+1L L*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 ; }
例题