给定数组 A , B A,B A,B,快速求二元卷积
C k = ∑ i ⊕ j A i B j C_k=\sum_{i\oplus j} A_iB_j Ck=i⊕j∑AiBj
其中 ⊕ \oplus ⊕ 是 or,and,xor
我们考虑类比 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[A∣B]=fwt[A]∗fwt[B]
其中 A ∣ B A|B A∣B 是或卷积。
我们是怎么造出它的呢?我们注意到了一个性质:
i ⊆ k and j ⊆ k ⇔ i ∪ j ⊆ k i\subseteq k \text{ and } j\subseteq k \Leftrightarrow i\cup j \subseteq k i⊆k and j⊆k⇔i∪j⊆k
因此我们可以尝试造出来
fwt [ A ] i = ∑ j ⊆ i A j \text{fwt}[A]_i = \sum_{j\subseteq i} A_j fwt[A]i=j⊆i∑Aj
( j ⊆ i j\subseteq i j⊆i 我们在实现时刻画成 j ∣ i = i j|i=i j∣i=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=i⊆k∑Aij⊆k∑Bj=i∪j⊆k∑AiBj=fwt[A∣B]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 0∼2d−1−1,2d−1∼2d−1 分别计算。不妨设这两部分分别是 A 0 , A 1 A_0,A_1 A0,A1。
考虑左半部分,可以发现对于 k ∈ [ 0 , 2 d − 1 ) k\in [0,2^{d-1}) k∈[0,2d−1) 满足 i ⊆ k i\subseteq k i⊆k 的 i i i 也只能在 [ 0 , 2 d − 1 ) [0,2^{d-1}) [0,2d−1) 中,所以 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]0∼2d−1−1=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] i⊆k∪{d}∑ai=i⊆k∑ai+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]2d−1∼2d−1=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[A1′−A0′]
所以说
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:好看!
差不多,只需要考虑将最重要的式子改为
k ⊆ i and k ⊆ j ⇔ k ⊆ i ∩ j k\subseteq i \text{ and } k\subseteq j \Leftrightarrow k\subseteq i\cap j k⊆i and k⊆j⇔k⊆i∩j
所以最后是
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)]);}
把异或记为 ⊕ \oplus ⊕。
这次我们注意到的性质是
( i ⊕ j ) ∩ k = ( i ∩ k ) ⊕ ( j ∩ k ) (i\oplus j)\cap k=(i\cap k)\oplus (j\cap k) (i⊕j)∩k=(i∩k)⊕(j∩k)
然后我们又注意到
∣ i ⊕ j ∣ ≡ ∣ i ∣ + ∣ j ∣ ( m o d 2 ) |i\oplus j|\equiv |i|+|j| \pmod 2 ∣i⊕j∣≡∣i∣+∣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=∣j∩i∣≡0(mod2)∑Aj−∣j∩i∣≡1(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=⎝⎛∣j∩i∣≡0(mod2)∑Aj−∣j∩i∣≡1(mod2)∑Aj⎠⎞⎝⎛∣k∩i∣≡0(mod2)∑Bj−∣k∩i∣≡1(mod2)∑Bj⎠⎞=fwt[A⊕B]
接下来我们需要求出 fwt [ A ] \text{fwt}[A] fwt[A],当 k < 2 d − 1 k<2^{d-1} k<2d−1,则
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=∣j∩k∣≡0(mod2)∑Aj−∣j∩k∣≡1(mod2)∑Aj=fwt[A0]k+fwt[A1]k
当 k ≥ 2 d − 1 k\ge 2^{d-1} k≥2d−1 时,
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=∣j∩k∣≡0(mod2)∑Aj−∣j∩k∣≡1(mod2)∑Aj=j<2d−1,∣j∩k∣≡0(mod2)∑Aj+j≥2d−1,∣(j−{d})∩(k−{d})∣≡1(mod2)∑Aj−j<2d−1,∣j∩k∣≡1(mod2)∑Aj−j≥2d−1,∣(j−{d})∩(k−{d})∣≡0(mod2)∑Aj=fwt[A0]k−fwt[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;}
}
#include
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 i∣j=k∑aibj
对 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 1≤n≤20,0≤ai,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 i∣j=k⇔∣i∣+∣j∣=∣k∣ and i∣j=k
所以我们对数组 a a a 做出 A ∣ i ∣ , i = a i A_{|i|,i}=a_i A∣i∣,i=ai 的映射, b b b 也同样,接下来我们对这 n + 1 n+1 n+1 个数组分别 fwt,然后枚举 ∣ i ∣ , ∣ j ∣ |i|,|j| ∣i∣,∣j∣,将 A ∣ i ∣ A_{|i|} A∣i∣ 和 B ∣ j ∣ B_{|j|} B∣j∣ 按位乘加到 ans ∣ i ∣ + ∣ j ∣ \text{ans}_{|i|+|j|} ans∣i∣+∣j∣ 中,最后对答案的 n + 1 n+1 n+1 个数组分别 ifwt,得到答案。复杂度 O ( 2 n × n ) O(2^n\times n) O(2n×n)。
#include
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;
}
有一个 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 1≤n≤20,1≤m≤105。
设每列的数是 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=1∑mmin{∣ai⊕S∣,n−∣ai⊕S∣}
令 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∣,n−∣i∣},则
f S = ∑ i = 1 m g i h i ⊕ S f_S=\sum_{i=1}^m g_ih_{i\oplus S} fS=i=1∑mgihi⊕S
也即
f S = ∑ i ⊕ j = S g i h j f_S=\sum_{i\oplus j=S} g_ih_j fS=i⊕j=S∑gihj
使用 FWT 加速计算,最后取 f f f 数组的全局 min,复杂度 O ( m + 2 n × n ) O(m+2^n\times n) O(m+2n×n)。
#include
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;
}