FWT求解的是一类问题:\( a[i] = \sum\limits_{j\bigoplus k=i}^{} b[j]*c[k] \)
其中,\( \bigoplus \) 可以是 or,and,xor
三种问题的解决思路都是对多项式 \( a \) 构造一个 \( a' \),令 \( a' = b' * c' \);
那么只需要把 \( b \) 变换成 \( b' \),\( c \) 变换成 \( c' \),然后乘出 \( a' \),再逆变换得到 \( a \);
下面问题就变成如何快速(logn)求 \( b \) 到 \( b' \) 的变换,这个变换就是 FWT;
始终要记住进行位运算的是位置(角标)而不是值;
一、or
构造 \( a'[i] = \sum\limits_{j|i=i}^{} a[j] \)
1.正变换
考虑把 \( a \) 分成前后两个部分 \( a0 \) 和 \( a1 \),先分别递归下去做好,得到 \( a0' \) 和 \( a1' \);
可以发现,\( a0' \) 和 \( a1' \) 的位置(角标)数字上唯一不同就是最高位是0或1;
但递归下去做的时候,\( a0' \) 和 \( a1' \) 的位置数字相当与去掉了最高位(因为折半了);
所以合并的时候,关键要考虑到最高位的0和1的不同:
(1) 对于 \( a' \) 的一个位置 \( i \) ,如果它在前半部分,那么它可以直接继承 \( a0'[i] \);
而 \( a1'[i]\) 由于实际上 \( i \) 还应该加上最高位的1,or 运算使它能贡献的位置最高位也是1,但 \( i \) 的最高位是0,所以不贡献给 \( a'[i] \) ;
(2)对于后半部分的 \( i \) ,\( a0'[i] \) 和 \( a1'[i] \) 都会对它产生贡献,因为两部分的位置数字都是 \( i \) 的子集;
所以可以得到:\( a' = \left ( a0' , a0'+a1' \right ) \)
递归的底层,只有一个元素的时候,\( a = a' \) ,于是我们可以递归做出正变换了;
当然,仿照 FFT 的写法即可,并不需要真的写递归函数,而且也不用蝴蝶变换;
2.逆变换
同样先考虑两个部分 \( a'0 \) 和 \( a'1 \) ,表示 \( a' \) 的前后部分;
已经做了 \( a' = \left ( a0' , a0'+a1' \right ) \)
现在要从 \( a' \) 拆出 \( a0' \) 和 \( a1' \)
那么 \( a0' = a'0 \)
而且 \( a1' = a'1 - a'0 \)
知道了 \( a0' \) 和 \( a0' \) ,就可以继续递归求解 \( a0 \) 和 \( a1 \),二者合起来就可以得到 \( a \)
递归的底层,只有一个元素的时候,\( a' = a \) ,于是我们可以递归作出逆变换了;
void fwt1(int *a,int tp)//a'=(a0',a0'+a1') //a=(a0',a1'-a0') { for(int mid=1;mid<lim;mid<<=1) for(int j=0,len=(mid<<1);j<lim;j+=len) for(int k=0;k<mid;k++) a[j+mid+k]=upt(a[j+mid+k]+tp*a[j+k]); }