FWT 学习笔记

功能

给定数组 A , B A,B A,B,快速求二元卷积

C k = ∑ i ⊕ j A i B j C_k=\sum_{i\oplus j} A_iB_j Ck=ijAiBj

其中 ⊕ \oplus 是 or,and,xor

OR

我们考虑类比 fft。具体地说,fft 时我们有一个 fft [ A ] \text{fft}[A] fft[A] 来求所有单位根的点值,然后用一个 ifft [ A ] \text{ifft}[A] ifft[A] 来将其还原。也就是说

fft [ A × B ] = fft [ A ] ∗ fft [ B ] \text{fft}[A\times B] = \text{fft}[A]*\text{fft}[B] fft[A×B]=fft[A]fft[B]

这里 ∗ * 是按位乘。可以发现还有一些性质,比如

fft [ A + B ] = fft [ A ] + fft [ B ] \text{fft}[A+B]=\text{fft}[A]+\text{fft}[B] fft[A+B]=fft[A]+fft[B]

之类的,所以 fft [ A ] \text{fft}[A] fft[A] 被称为一个 线性变换

现在我们要找到一个 fwt [ A ] \text{fwt}[A] fwt[A],它也是线性变换,且满足

fwt [ A ∣ B ] = fwt [ A ] ∗ fwt [ B ] \text{fwt}[A|B]=\text{fwt}[A]*\text{fwt}[B] fwt[AB]=fwt[A]fwt[B]

其中 A ∣ B A|B AB 是或卷积。

我们是怎么造出它的呢?我们注意到了一个性质:

i ⊆ k  and  j ⊆ k ⇔ i ∪ j ⊆ k i\subseteq k \text{ and } j\subseteq k \Leftrightarrow i\cup j \subseteq k ik and jkijk

因此我们可以尝试造出来

fwt [ A ] i = ∑ j ⊆ i A j \text{fwt}[A]_i = \sum_{j\subseteq i} A_j fwt[A]i=jiAj

j ⊆ i j\subseteq i ji 我们在实现时刻画成 j ∣ i = i j|i=i ji=i

这样我们考虑性质

( fwt [ A ] ∗ fwt [ B ] ) k = ∑ i ⊆ k A i ∑ j ⊆ k B j = ∑ i ∪ j ⊆ k A i B j = fwt [ A ∣ B ] k (\text{fwt}[A]*\text{fwt}[B])_k=\sum_{i\subseteq k} A_i \sum_{j\subseteq k} B_j=\sum_{i\cup j \subseteq k} A_iB_j=\text{fwt}[A|B]_k (fwt[A]fwt[B])k=ikAijkBj=ijkAiBj=fwt[AB]k

并且很容易发现

fwt [ A ] + fwt [ B ] = fwt [ A + B ] \text{fwt}[A]+\text{fwt}[B]=\text{fwt}[A+B] fwt[A]+fwt[B]=fwt[A+B]

所以说这是一个线性变换。

现在我们需要求出这个变换,我们考虑进行 dp,考虑最高位 d d d 是否在 k k k 中,也就是说我们对 0 ∼ 2 d − 1 − 1 , 2 d − 1 ∼ 2 d − 1 0\sim 2^{d-1}-1,2^{d-1}\sim 2^d-1 02d11,2d12d1 分别计算。不妨设这两部分分别是 A 0 , A 1 A_0,A_1 A0,A1

考虑左半部分,可以发现对于 k ∈ [ 0 , 2 d − 1 ) k\in [0,2^{d-1}) k[0,2d1) 满足 i ⊆ k i\subseteq k ik i i i 也只能在 [ 0 , 2 d − 1 ) [0,2^{d-1}) [0,2d1) 中,所以 fwt [ A ] 0 ∼ 2 d − 1 − 1 = fwt [ A 0 ] \text{fwt}[A]_{0\sim 2^{d-1}-1}=\text{fwt}[A_0] fwt[A]02d11=fwt[A0]

对于右半部分,考虑枚举 d d d 是否在 i i i 中,书面语言来写就是

∑ i ⊆ k ∪ { d } a i = ∑ i ⊆ k a i + a i ∪ { d } = fwt [ A 0 ] + fwt [ A 1 ] \sum_{i\subseteq k\cup \{d\}} a_i=\sum_{i\subseteq k} a_i+a_{i\cup \{d\}} = \text{fwt}[A_0]+\text{fwt}[A_1] ik{d}ai=ikai+ai{d}=fwt[A0]+fwt[A1]

所以说 fwt [ A ] 2 d − 1 ∼ 2 d − 1 = fwt [ A 0 ] + fwt [ A 1 ] \text{fwt}[A]_{2^{d-1}\sim 2^{d}-1}=\text{fwt}[A_0]+\text{fwt}[A_1] fwt[A]2d12d1=fwt[A0]+fwt[A1]

最后考虑边界也就是 n = 0 n=0 n=0 的时候显然此时 fwt [ A ] = A \text{fwt}[A]=A fwt[A]=A

所以说我们的递推式是

fwt [ A ] = { ( fwt [ A 0 ] , fwt [ A 0 ] + fwt [ A 1 ] ) n > 0 A n = 0 \text{fwt}[A] = \left\{ \begin{aligned} & (\text{fwt}[A_0],\text{fwt}[A_0]+\text{fwt}[A_1]) & n>0 \\ & A & n=0 \end{aligned} \right. fwt[A]={(fwt[A0],fwt[A0]+fwt[A1])An>0n=0

这里 ( A , B ) (A,B) (A,B) 就是直接拼接。

现在我们要考虑如何还原,此时我们是知道了 A 0 ′ = fwt [ A 0 ] A_0'=\text{fwt}[A_0] A0=fwt[A0] A 1 ′ = fwt [ A 0 ] + fwt [ A 1 ] A_1'=\text{fwt}[A_0]+\text{fwt}[A_1] A1=fwt[A0]+fwt[A1] (两个 ′ ' 不是求导),要求 A 0 , A 1 A_0,A_1 A0,A1,很明显

A 0 = ifwt [ A 0 ′ ] , A 1 = ifwt [ A 1 ′ − A 0 ′ ] A_0=\text{ifwt} [A_0'], A_1=\text{ifwt} [A_1'-A_0'] A0=ifwt[A0],A1=ifwt[A1A0]

所以说

ifwt [ A ] = { ( ifwt [ A 0 ] , ifwt [ A 1 ] − ifwt [ A 0 ] ) n > 0 A n = 0 \text{ifwt}[A] = \left\{ \begin{aligned} & (\text{ifwt}[A_0],\text{ifwt}[A_1]-\text{ifwt}[A_0]) & n>0 \\ & A & n=0 \end{aligned} \right. ifwt[A]={(ifwt[A0],ifwt[A1]ifwt[A0])An>0n=0

实现时可以将两个揉在一起:

void FWT_or(int* f,int x){for(int l=2;l<=N;l<<=1)for(int i=0;i<N;i+=l)for(int j=0;j<(l>>1);++j)ADD(f[i+j+(l>>1)],1ll*x*f[i+j]%MOD);}

Q:压成一行的目的是
A:好看!

AND

差不多,只需要考虑将最重要的式子改为

k ⊆ i  and  k ⊆ j ⇔ k ⊆ i ∩ j k\subseteq i \text{ and } k\subseteq j \Leftrightarrow k\subseteq i\cap j ki and kjkij

所以最后是

fwt [ A ] = { ( fwt [ A 0 ] + fwt [ A 1 ] , fwt [ A 1 ] ) n > 0 A n = 0 \text{fwt}[A] = \left\{ \begin{aligned} & (\text{fwt}[A_0]+\text{fwt}[A_1],\text{fwt}[A_1]) & n>0 \\ & A & n=0 \end{aligned} \right. fwt[A]={(fwt[A0]+fwt[A1],fwt[A1])An>0n=0

ifwt [ A ] = { ( ifwt [ A 0 ] − ifwt [ A 1 ] , ifwt [ A 1 ] ) n > 0 A n = 0 \text{ifwt}[A] = \left\{ \begin{aligned} & (\text{ifwt}[A_0]-\text{ifwt}[A_1], \text{ifwt}[A_1]) & n>0 \\ & A & n=0 \end{aligned} \right. ifwt[A]={(ifwt[A0]ifwt[A1],ifwt[A1])An>0n=0

实现:

void FWT_and(int* f,int x){for(int l=2;l<=N;l<<=1)for(int i=0;i<N;i+=l)for(int j=0;j<(l>>1);++j)ADD(f[i+j],1ll*x*f[i+j+(l>>1)]);}

XOR

把异或记为 ⊕ \oplus

这次我们注意到的性质是

( i ⊕ j ) ∩ k = ( i ∩ k ) ⊕ ( j ∩ k ) (i\oplus j)\cap k=(i\cap k)\oplus (j\cap k) (ij)k=(ik)(jk)

然后我们又注意到

∣ i ⊕ j ∣ ≡ ∣ i ∣ + ∣ j ∣ ( m o d 2 ) |i\oplus j|\equiv |i|+|j| \pmod 2 iji+j(mod2)

所以如果我们设

fwt [ A ] i = ∑ ∣ j ∩ i ∣ ≡ 0 ( m o d 2 ) A j − ∑ ∣ j ∩ i ∣ ≡ 1 ( m o d 2 ) A j \text{fwt}[A]_i=\sum_{|j\cap i|\equiv 0 \pmod 2} A_j - \sum_{|j\cap i|\equiv 1 \pmod 2} A_j fwt[A]i=ji0(mod2)Ajji1(mod2)Aj

( fwt [ A ] ∗ fwt [ B ] ) i = ( ∑ ∣ j ∩ i ∣ ≡ 0 ( m o d 2 ) A j − ∑ ∣ j ∩ i ∣ ≡ 1 ( m o d 2 ) A j ) ( ∑ ∣ k ∩ i ∣ ≡ 0 ( m o d 2 ) B j − ∑ ∣ k ∩ i ∣ ≡ 1 ( m o d 2 ) B j ) = fwt [ A ⊕ B ] \begin{aligned} & (\text{fwt}[A]*\text{fwt}[B])_i \\ & =\left( \sum_{|j\cap i|\equiv 0 \pmod 2} A_j - \sum_{|j\cap i|\equiv 1 \pmod 2} A_j \right)\left( \sum_{|k\cap i|\equiv 0 \pmod 2} B_j - \sum_{|k\cap i|\equiv 1 \pmod 2} B_j \right) \\ & = \text{fwt}[A\oplus B] \end{aligned} (fwt[A]fwt[B])i=ji0(mod2)Ajji1(mod2)Ajki0(mod2)Bjki1(mod2)Bj=fwt[AB]

接下来我们需要求出 fwt [ A ] \text{fwt}[A] fwt[A],当 k < 2 d − 1 k<2^{d-1} k<2d1,则

fwt [ A ] k = ∑ ∣ j ∩ k ∣ ≡ 0 ( m o d 2 ) A j − ∑ ∣ j ∩ k ∣ ≡ 1 ( m o d 2 ) A j = fwt [ A 0 ] k + fwt [ A 1 ] k \begin{aligned} & \text{fwt}[A]_k \\ & =\sum_{|j\cap k|\equiv 0 \pmod 2} A_j - \sum_{|j\cap k|\equiv 1 \pmod 2} A_j \\ & =\text{fwt}[A_0]_k+\text{fwt}[A_1]_k \end{aligned} fwt[A]k=jk0(mod2)Ajjk1(mod2)Aj=fwt[A0]k+fwt[A1]k

k ≥ 2 d − 1 k\ge 2^{d-1} k2d1 时,

fwt [ A ] k = ∑ ∣ j ∩ k ∣ ≡ 0 ( m o d 2 ) A j − ∑ ∣ j ∩ k ∣ ≡ 1 ( m o d 2 ) A j = ∑ j < 2 d − 1 , ∣ j ∩ k ∣ ≡ 0 ( m o d 2 ) A j + ∑ j ≥ 2 d − 1 , ∣ ( j − { d } ) ∩ ( k − { d } ) ∣ ≡ 1 ( m o d 2 ) A j − ∑ j < 2 d − 1 , ∣ j ∩ k ∣ ≡ 1 ( m o d 2 ) A j − ∑ j ≥ 2 d − 1 , ∣ ( j − { d } ) ∩ ( k − { d } ) ∣ ≡ 0 ( m o d 2 ) A j = fwt [ A 0 ] k − fwt [ A 1 ] k \begin{aligned} & \text{fwt}[A]_k \\ & =\sum_{|j\cap k|\equiv 0 \pmod 2} A_j - \sum_{|j\cap k|\equiv 1 \pmod 2} A_j \\ & =\sum_{j<2^{d-1},|j\cap k|\equiv 0 \pmod 2} A_j + \sum_{j\ge 2^{d-1},|(j-\{d\})\cap (k-\{d\})|\equiv 1 \pmod 2} A_j - \sum_{j<2^{d-1},|j\cap k|\equiv 1 \pmod 2} A_j - \sum_{j\ge 2^{d-1},|(j-\{d\})\cap (k-\{d\})|\equiv 0 \pmod 2} A_j \\ & = \text{fwt}[A_0]_k-\text{fwt}[A_1]_k \end{aligned} fwt[A]k=jk0(mod2)Ajjk1(mod2)Aj=j<2d1,jk0(mod2)Aj+j2d1,(j{d})(k{d})1(mod2)Ajj<2d1,jk1(mod2)Ajj2d1,(j{d})(k{d})0(mod2)Aj=fwt[A0]kfwt[A1]k

(这也是为啥 fwt 的式子要写成模 0 和模 1 相减,因为写成别的形式就写不出来)

所以

fwt [ A ] = { ( fwt [ A 0 ] + fwt [ A 1 ] , fwt [ A 0 ] − fwt [ A 1 ] ) n > 0 A n = 0 \text{fwt}[A] = \left\{ \begin{aligned} & (\text{fwt}[A_0]+\text{fwt}[A_1],\text{fwt}[A_0]-\text{fwt}[A_1]) & n>0 \\ & A & n=0 \end{aligned} \right. fwt[A]={(fwt[A0]+fwt[A1],fwt[A0]fwt[A1])An>0n=0

然后也容易得到

ifwt [ A ] = { 1 2 ( ifwt [ A 0 ] + ifwt [ A 1 ] , ifwt [ A 0 ] − ifwt [ A 1 ] ) n > 0 A n = 0 \text{ifwt}[A] = \left\{ \begin{aligned} & \frac{1}{2}(\text{ifwt}[A_0]+\text{ifwt}[A_1],\text{ifwt}[A_0]-\text{ifwt}[A_1]) & n>0 \\ & A & n=0 \end{aligned} \right. ifwt[A]=21(ifwt[A0]+ifwt[A1],ifwt[A0]ifwt[A1])An>0n=0

实现:

void FWT_xor(int* f,int X) {
	for(int l=2;l<=N;l<<=1)for(int i=0;i<N;i+=l)for(int j=0;j<(l>>1);++j){
		int x=f[i+j],y=f[i+j+(l>>1)];f[i+j]=add(x,y),f[i+j+(l>>1)]=dec(x,y);
		if(X==-1){f[i+j]=1ll*f[i+j]*i2%MOD,f[i+j+(l>>1)]=1ll*f[i+j+(l>>1)]*i2%MOD;}
}

例题

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

实现

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int MAXN = 200005, MOD = 998244353, i2 = (MOD + 1) >> 1;
void ADD(int& x, int y) { x += y; if (x >= MOD) x -= MOD; }
void DEC(int& x, int y) { x -= y; if (x < 0) x += MOD; }
inline int add(int x, int y) { return x + y < MOD ? x + y : x + y - MOD; }
inline int dec(int x, int y) { return x - y < 0 ? x - y + MOD : x - y; }

int D, N, A[MAXN], B[MAXN], a[MAXN], b[MAXN];

void in() { for (int i = 0; i < N; ++i) a[i] = A[i], b[i] = B[i]; }
void out() { for (int i = 0; i < N; ++i) printf("%d ", a[i]); putchar('\n'); }
void mul() { for (int i = 0; i < N; ++i) a[i] = 1ll * a[i] * b[i] % MOD; }
void FWT_or(int* f, int x) {
	for (int l = 2; l <= N; l <<= 1) for (int i = 0; i < N; i += l) for (int j = 0; j < (l >> 1); ++j) ADD(f[i + j + (l >> 1)], 1ll * x * f[i + j] % MOD);
}
void FWT_and(int* f, int x) {
	for (int l = 2; l <= N; l <<= 1) for (int i = 0; i < N; i += l) for (int j = 0; j < (l >> 1); ++j) ADD(f[i + j], 1ll * x * f[i + j + (l >> 1)] % MOD);
}
void FWT_xor(int* f, int X) {
	for (int l = 2; l <= N; l <<= 1) {
		for (int i = 0; i < N; i += l) for (int j = 0; j < (l >> 1); ++j) {
			int x = f[i + j], y = f[i + j + (l >> 1)];
			f[i + j] = add(x, y), f[i + j + (l >> 1)] = dec(x, y);
			if (X == -1) {
				f[i + j] = 1ll * f[i + j] * i2 % MOD;
				f[i + j + (l >> 1)] = 1ll * f[i + j + (l >> 1)] * i2 % MOD;
			}
		}
	}
}

int main() {
	scanf("%d", &D), N = 1 << D;
	for (int i = 0; i < N; ++i) scanf("%d", &A[i]);
	for (int i = 0; i < N; ++i) scanf("%d", &B[i]);
	in(), FWT_or(a, 1), FWT_or(b, 1), mul(), FWT_or(a, MOD - 1), out();
	in(), FWT_and(a, 1), FWT_and(b, 1), mul(), FWT_and(a, MOD - 1), out();
	in(), FWT_xor(a, 1), FWT_xor(b, 1), mul(), FWT_xor(a, -1), out();
	return 0;
}

【模板】子集卷积

题意

给定数组 a , b a,b a,b,求数组 c c c 满足

c k = ∑ i & j = 0  and  i ∣ j = k a i b j c_k=\sum_{i\&j=0 \text{ and } i|j=k} a_ib_j ck=i&j=0 and ij=kaibj

1 0 9 + 7 10^9+7 109+7 取模。

数据范围:两个数组长度为 2 n 2^n 2n 1 ≤ n ≤ 20 , 0 ≤ a i , b i < 1 0 9 + 9 1\le n\le 20, 0\le a_i,b_i<10^9+9 1n20,0ai,bi<109+9

题解

注意到重要性质

i & j = 0  and  i ∣ j = k ⇔ ∣ i ∣ + ∣ j ∣ = ∣ k ∣  and  i ∣ j = k i\&j=0 \text{ and } i|j=k \Leftrightarrow |i|+|j|=|k| \text{ and } i|j=k i&j=0 and ij=ki+j=k and ij=k

所以我们对数组 a a a 做出 A ∣ i ∣ , i = a i A_{|i|,i}=a_i Ai,i=ai 的映射, b b b 也同样,接下来我们对这 n + 1 n+1 n+1 个数组分别 fwt,然后枚举 ∣ i ∣ , ∣ j ∣ |i|,|j| i,j,将 A ∣ i ∣ A_{|i|} Ai B ∣ j ∣ B_{|j|} Bj 按位乘加到 ans ∣ i ∣ + ∣ j ∣ \text{ans}_{|i|+|j|} ansi+j 中,最后对答案的 n + 1 n+1 n+1 个数组分别 ifwt,得到答案。复杂度 O ( 2 n × n ) O(2^n\times n) O(2n×n)

实现

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int MAXN = 2000005, MOD = 1e9 + 9;
void ADD(int& x, int y) { x += y; if (x >= MOD) x -= MOD; }
int D, N, A[21][MAXN], B[21][MAXN], C[21][MAXN];
void FWT(int* f, int x) {
	for (int l = 2; l <= N; l <<= 1)
		for (int i = 0; i < N; i += l)
			for (int j = 0; j < (l >> 1); ++j)
				ADD(f[i + j + (l >> 1)], 1ll * f[i + j] * x % MOD);
}
int main() {
	scanf("%d", &D), N = 1 << D;
	for (int i = 0; i < N; ++i) scanf("%d", &A[__builtin_popcount(i)][i]);
	for (int i = 0; i < N; ++i) scanf("%d", &B[__builtin_popcount(i)][i]);
	for (int i = 0; i <= D; ++i) FWT(A[i], 1), FWT(B[i], 1);
	for (int i = 0; i <= D; ++i) for (int j = 0; j <= D - i; ++j)
		for (int k = 0; k < N; ++k) ADD(C[i + j][k], 1ll * A[i][k] * B[j][k] % MOD);
	for (int i = 0; i <= D; ++i) FWT(C[i], MOD - 1);
	for (int i = 0; i < N; ++i) printf("%d ", C[__builtin_popcount(i)][i]); putchar('\n');
	return 0;
}

CF662C Binary Table

题意

有一个 n n n m m m 列的表格,每个元素都是 0 / 1 0/1 0/1 ,每次操作可以选择一行或一列,把 0 / 1 0/1 0/1 翻转,即把 0 0 0 换为 1 1 1 ,把 1 1 1 换为 0 0 0 。请问经过若干次操作后,表格中最少有多少个 1 1 1

数据范围: 1 ≤ n ≤ 20 , 1 ≤ m ≤ 1 0 5 1\le n\le 20, 1\le m\le 10^5 1n20,1m105

题解

设每列的数是 a i a_i ai,对于行状态 S S S,这次的答案是

f S = ∑ i = 1 m min ⁡ { ∣ a i ⊕ S ∣ , n − ∣ a i ⊕ S ∣ } f_S=\sum_{i=1}^m \min\{|a_i\oplus S|, n-|a_i\oplus S|\} fS=i=1mmin{aiS,naiS}

g i = ∑ j = 1 m [ a j = i ] g_{i}=\sum_{j=1}^m [a_j=i] gi=j=1m[aj=i] 也即 a i a_i ai 的桶, h i = min ⁡ { ∣ i ∣ , n − ∣ i ∣ } h_i=\min\{|i|,n-|i|\} hi=min{i,ni},则

f S = ∑ i = 1 m g i h i ⊕ S f_S=\sum_{i=1}^m g_ih_{i\oplus S} fS=i=1mgihiS

也即

f S = ∑ i ⊕ j = S g i h j f_S=\sum_{i\oplus j=S} g_ih_j fS=ij=Sgihj

使用 FWT 加速计算,最后取 f f f 数组的全局 min,复杂度 O ( m + 2 n × n ) O(m+2^n\times n) O(m+2n×n)

代码

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int MAXN = 2000005;

int D, N, M, a[MAXN]; char S[MAXN];
ll f[MAXN], g[MAXN], ans = 2147483647;
void FWT(ll* f, int X) {
	for (int l = 2; l <= N; l <<= 1) for (int i = 0; i < N; i += l) for (int j = 0; j < (l >> 1); ++j) {
		ll x = f[i + j], y = f[i + j + (l >> 1)];
		f[i + j] = x + y, f[i + j + (l >> 1)] = x - y;
		if (X == -1) f[i + j] /= 2, f[i + j + (l >> 1)] /= 2;
	}
}
int main() {
	scanf("%d%d", &D, &M), N = 1 << D; int cnt;
	for (int i = 0; i < D; ++i) {
		scanf("%s", S);
		for (int j = 0; j < M; ++j) if (S[j] == '1') a[j] |= 1 << i;
	}
	for (int i = 0; i < M; ++i) ++f[a[i]];
	for (int i = 0; i < N; ++i) cnt = __builtin_popcount(i), g[i] = min(cnt, D - cnt);
	FWT(f, 1), FWT(g, 1); for (int i = 0; i < N; ++i) f[i] = f[i] * g[i]; FWT(f, -1);
	for (int i = 0; i < N; ++i) ans = min(ans, f[i]);
	printf("%lld\n", ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值