【模板】快速沃尔什变换 FWT

对 @wangrx 博客的抄写,这里感谢他教会我卷积。

FWT 重要性质:处理哪一位(枚举 \(len,k\)) 的顺序无关紧要,因为每一层的处理与长度无关。

很重要!

problem

解决形如

\[c_k=\sum_{i\oplus j=k}a_ib_j. \]

的卷积式子,这里关注 \(\oplus=\{\text{or},\text{and},\text{xor}\}\)

solution-xor

\(c=a\otimes_{\text{xor}} b\),表示 \(\text{xor}\) 卷积。显然有交换律,结合律,分配律

\(a,b,c\) 按照二进制最高位奇偶分段为 \(a_0,a_1,b_0,b_1,c_0,c_1\)

则有:

\[\begin{aligned} c_0&=(a_0\otimes_{\text{xor}}b_0)+(a_1\otimes_{\text{xor}}b_1).\\ c_1&=(a_0\otimes_{\text{xor}}b_1)+(a_1\otimes_{\text{xor}}b_0). \end{aligned}\]

观察到:

\[\begin{aligned} (a_0+a_1)\otimes_{\text{xor}}(b_0+b_1)&=a_0\otimes_{\text{xor}}b_0+a_1\otimes_{\text{xor}}b_1+a_0\otimes_{\text{xor}}b_1+a_1\otimes_{\text{xor}}b_0.\\ (a_0-a_1)\otimes_{\text{xor}}(b_0-b_1)&=a_0\otimes_{\text{xor}}b_0+a_1\otimes_{\text{xor}}b_1-a_0\otimes_{\text{xor}}b_1-a_1\otimes_{\text{xor}}b_0. \end{aligned}\]

记这两个为 \(d_0,d_1\),我们的思路是算出 \(d_0,d_1\),然后 \(c_0=\frac{1}{2}(d_0+d_1),c_1=\frac{1}{2}(d_0-d_1)\)

所以:

\[\begin{matrix} &a_0 &a_1 &b_0 &b_1\\ &\Downarrow &\Downarrow &\Downarrow &\Downarrow \\ &a_0+a_1 &a_0-a_1 &b_0+b_1 &b_0-b_1 \end{matrix}\]

相当于我们篡改了我们要卷的东西,然后计算答案的时候改回去变成对的。假设继续递归算了 \(d_0,d_1\),则回溯时

\[\begin{matrix} &c_0 &c_1\\ &\Uparrow &\Uparrow \\ &\frac{1}{2}(d_0+d_1) &\frac{1}{2}(d_0-d_1) \end{matrix}\]

嗯嗯做完了。实际代码实现会有点鬼畜,不过知道这些式子怎么来的就行了 QAQ

复杂度 \(O(n\log n)\) 呐。

点击查看代码
const int P=998244353;
void red(LL&x){x=(x%P+P)%P;}
void fwt_xor(LL *f,int n,int op){
	for(int len=2,k=1;len<=n;len<<=1,k<<=1){
		for(int i=0;i<n;i+=len){
			for(int j=0;j<k;j++){
				LL ta=f[i+j],tb=f[i+j+k];
				red(f[i+j]=(ta+tb)*op);
				red(f[i+j+k]=(ta-tb)*op);
			}
		}
	}
}
fwt_xor(a,n,1),fwt_xor(b,n,1),multiple(a,b,c,n),fwt_xor(c,n,499122177);
//multiple 是逐位相乘,499122177 是 2 的逆元

solution-or

\(c=a\otimes_{\text{or}} b\),表示 \(\text{or}\) 卷积。显然有交换律,结合律,分配律。

我们复读一遍上面的过程:因为

\[\begin{aligned} c_0&=a_0\otimes_{\text{or}}b_0.\\ c_1&=a_0\otimes_{\text{or}}b_1+a_1\otimes_{\text{or}}b_0+a_1\otimes_{\text{or}}b_1. \end{aligned}\]

蝴蝶变换来啦~

\[\begin{aligned} a_0\otimes_{\text{or}}b_0&=a_0\otimes_{\text{or}}b_0.\\ (a_0+a_1)\otimes_{\text{or}}(b_0+b_1)&=a_0\otimes_{\text{or}}b_0+a_1\otimes_{\text{or}}b_1+a_0\otimes_{\text{or}}b_1+a_1\otimes_{\text{or}}b_0. \end{aligned}\]

\[\begin{matrix} &a_0 &a_1 &b_0 &b_1\\ &\Downarrow &\Downarrow &\Downarrow &\Downarrow \\ &a_0 &a_0+a_1 &b_0 &b_0+b_1 \end{matrix}\]

\[\begin{matrix} &c_0 &c_1\\ &\Uparrow &\Uparrow \\ &d_0 &d_1-d_0 \end{matrix}\]

点击查看代码
void fwt_or(LL *f,int n,int op){
	for(int len=2,k=1;len<=n;len<<=1,k<<=1){
		for(int i=0;i<n;i+=len){
			for(int j=0;j<k;j++)
				red(f[i+j+k]+=f[i+j]*op);
		}
	}
}
fwt_or(a,n,1),fwt_or(b,n,1),multiple(a,b,c,n),fwt_or(c,n,-1);

solution-and

这次是真的反过来直接上结论了 QAQ

\[\begin{matrix} &a_0 &a_1 &b_0 &b_1\\ &\Downarrow &\Downarrow &\Downarrow &\Downarrow \\ &a_0+a_1 &a_1 &b_0+b_1 &b_1 \end{matrix}\]

\[\begin{matrix} &c_0 &c_1\\ &\Uparrow &\Uparrow \\ &d_0-d_1 &d_1 \end{matrix}\]

代码都是反过来呢。

点击查看代码
void fwt_and(LL *f,int n,int op){
	for(int len=2,k=1;len<=n;len<<=1,k<<=1)
		for(int i=0;i<n;i+=len){
			for(int j=0;j<k;j++){
				red(f[i+j]+=f[i+j+k]*op);
		}
	}
}
fwt_and(a,n,1),fwt_and(b,n,1),multiple(a,b,c,n),fwt_and(c,n,-1);
posted @ 2022-11-16 20:09  caijianhong  阅读(23)  评论(0编辑  收藏  举报