FWT-快速沃尔什变换

FWT

这大概是十分基础的知识,然而我在模拟测中挂了 \(INF\) 次后终于不咕了

我们要求

\[c_i=\sum_{j\operatorname{qwq}k=i}a_jb_k \]

\(\operatorname{qwq}\) 是某种二元运算,在 \(FFT\) 中,\(\operatorname{qwq}\) 对应的是加法,而在 \(FWT\) 中,\(\operatorname{qwq}\) 通常表示 \(\operatorname{bitor},\operatorname{bitand},\operatorname{xor}\) 中的一个

那么我们当然可以沿用 \(FFT\) 中的思路,我们把 \(a\) 变成 \(fwt_a\),然后可以直接和 \(fwt_b\) 对应项相乘得出 \(fwt_c\),最后在逆变换回去就得出了 \(c\)

为了方便描述,我们用 \(\cup\) 表示序列的拼接,比如 \(\{1,2,3\}\cup\{ 2,3\}=\{1,2,3,2,3\}\)

结论

\(\operatorname{bitor}\)

直接构造 \(f[a]_i=\sum_{j|i=i}a_j\)\(a\)\(\operatorname{bitor}\) 意义下的 \(FWT\) 数组

然后发现直接对应点乘是对的

\[\begin{align*} f[a]_if[b]_i&=(\sum_{j|i=i}a_j)(\sum_{k|i=i}b_k) \\ &=\sum_{j|k|i=i}a_jb_k \\ &=f[c]_i \end{align*} \]

不难发现 \(f=f[0]\cup (f[0]+f[1])\)

其中,\(f[0]\) 表示下标最高位为 \(0\) 的序列,\(f[1]\) 表示下标最高位为 \(1\) 的序列

逆变换也一样,

\[a=a_0\cup(a_1-a_0) \]

于是可以写出来

for(int o=2,k=1;o<=n;o<<=1,k<<=1)
	for(int i=0;i<n;i+=o)
		for(int j=0;j<k;++j)
			f[i+j+k]+=f[i+j]*x;

\(x=\pm 1\) 分别表示正反变换

因为 \(FWT\) 都是基于奇偶分段的,递归的写法也都差不多,可以简记为 \(\operatorname{fwt\_for}\)

就像这样

#define fwt_for(n) for(int o=2,k=1;o<=n;o<<=1,k<<=1) for(int i=0;i<n;i+=o) for(int j=0;j<k;++j)

可以简略地写为

fwt_for(n) f[i+j+k]+=f[i+j]*x;

\(\operatorname{bitand}\)

不难发现这就是 \(\operatorname{bitor}\) 反过来,式子两边也反过来就可以了

fwt_for(n) f[i+j]+=f[i+j+k]*x;

\(\operatorname{xor}\)

异或稍微困难一些,逆变换是应该使用 \(x=\frac 12\)

这里直接给出结论之后再推导

fwt_for(n)
	f[i+j]+=f[i+j+k],
	f[i+j+k]=f[i+j]-f[i+j+k]*2,
	f[i+j]*=x,f[i+j+k]*=x;

如果长于压行的话完全可以写在一排

推导

思路都是一样的,我们进行奇偶分段

考虑下标最高位的为 \(0\) 的为 \(a_0,b_0,c_0\),为 \(1\) 的为 \(a_1,b_1,c_1\)

\(\operatorname{xor}\) 为例,我们容易知道

\[c_0=(a_0\operatorname{xor}b_0)+(a_1\operatorname{xor}b_1)\\ c_1=(a_0\operatorname{xor}b_1)+(a_1\operatorname{xor}b_0) \]

为了方便理解,我们在这里放出另外两种位运算的结果

\[c_0=(a_0\operatorname{bitand}b_0)+(a_1\operatorname{bitand}b_0)+(a_0\operatorname{bitand}b_1)\\ c_1=a_1\operatorname{bitand}b_1 \]

\[c_0=a_0\operatorname{bitor}b_0\\ c_1=(a_1\operatorname{bitor}b_0)+(a_0\operatorname{bitor}b_1)+(a_1\operatorname{bitor}b_1) \]

直接这样分治是 \(O(n^2)\)

考虑高精时一个 \(naive\) 的想法

\[\begin{align*} (a\times10^{\frac n2}+b)(c\times10^{\frac n2}+d)&=ac\times10^{n}+(ad+bc)\times10^{\frac n2}+bd\\ &=ac\times10^{n}+((a+b)(c+d)-ac-bd)\times10^{\frac n2}+bd \end{align*} \]

通过变换,我们减少了一次乘法(如果再减少就是 \(FFT\) 了)

同理,我们考虑

\[(a_0+a_1)\operatorname{xor}(b_0+b_1)=(a_0\operatorname{xor}b_0)+(a_1\operatorname{xor}b_0)+(a_0\operatorname{xor}b_1)+(a_1\operatorname{xor}b_1)\\ (a_0-a_1)\operatorname{xor}(b_0-b_1)=(a_0\operatorname{xor}b_0)+(a_1\operatorname{xor}b_0)-(a_0\operatorname{xor}b_1)-(a_1\operatorname{xor}b_1) \]

于是

\[c_0=\frac 12(((a_0+a_1)\operatorname{xor}(b_0+b_1))+((a_0-a_1)\operatorname{xor}(b_0-b_1)))\\ c_1=\frac 12(((a_0+a_1)\operatorname{xor}(b_0+b_1))-((a_0-a_1)\operatorname{xor}(b_0-b_1))) \]

然后就是 \(O(n\log n)\) 的了

子集卷积

我们常常遇到这样一个问题:

\[C_S=\sum_{i\operatorname{bitand}j=0\\i\operatorname{bitor}j=S}A_iB_j \]

如果我们暴力枚举每个子集,那么复杂度是 \(O(3^n)\) 的(\(n=\log S_{\operatorname{max}}\)

有了 \(FWT\),我们可以把复杂度优化到 \(O(n^22^n)\)

对于 \(i\operatorname{bitor}j=S\) 的约束我们直接做一个 \(\operatorname{bitor}\)\(FWT\) 就好了

对于 \(i\operatorname{bitand}j=0\) 的约束,感性理解一下就是两个集合不交,那么必然有

\[\operatorname{popcount}(i)+\operatorname{popcount}(j)=\operatorname{popcount}(S) \]

我们只需要把原本的 \(A_i\) 表示为 \(0,0\dots A_{\operatorname{popcount}(i),i}\dots0,0\)

也就是把原本的序列拆为 \(n\) 个序列,原本序列上的值 \(A_i\) 只在第 \(\operatorname{popcount}(i)\) 个序列上存储,然后我们发现直接枚举就可以了

for(int i=0;i<=n;++i)
	for(int j=0;j<=i;++j)
		for(int k=0;k<(1<<n);++k)
			c[i][k]+=a[j][k]*b[i-j][k];

注意一个细节,求解 \(\operatorname{popcount}\) 有一个显然的 \(O(n)\) 的做法,而更优的写法较为复杂,所以在实际运行中我们采用预处理 \(2^k\)\(\operatorname{popcount}\) 值,然后分段直接查,可以做到 \(O(1)\),在实际测试中吊打理论上 \(O(\alpha(n))\) 的做法

posted @ 2022-08-03 14:40  嘉年华_efX  阅读(57)  评论(0)    收藏  举报