输入一个长度为 8 的数组 a[0] ... a[7]
0 000 {}
1 001 {0}
2 010 {1}
3 011 {0, 1}
4 100 {2}
5 101 {0, 2}
6 110 {1, 2}
7 111 {0, 1, 2}
210
输出一个长度为 8 的数组,其中第i项,表示i的所有子集之和 如果 j 满足 (i&j) == j 那么 j 是 i 的子集
0: a[0]
1: a[1] + a[0]
2: a[2] + a[0]
3: a[3] + a[2] + a[1] + a[0]
4: a[4] + a[0]
5: a[5] + a[4] + a[1] + a[0]
6: a[6] + a[4] + a[2] + a[0]
7: a[7] + a[6] + a[5] + a[4] + a[3] + a[2] + a[1] + a[0]
输入数组大小是 n = 2**m
直接暴力 4**m = n^2
for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if ((i & j) == j) { b[i] += a[j]; } } }
枚举子集 3**m = n ** (log(3) / log(2) = n ** 1.58)
for (int i = 0; i < n; i++) { for (int j = i;; --j &= i) { b[i] += a[j]; if (j == 0) { break; } } }
子集和变换 2**m * m = n log n
0
1
2
3
4
5
6
7
0
1 0
2
3 2
4
5 4
6
7 6
0
1 0
2 0
3 2 1 0
4
5 4
6 4
7 6 5 4
0
1 0
2 0
3 2 1 0
4 0
5 4 1 0
6 4 2 0
7 6 5 4 3 2 1 0
for (int j = 0; j < m; j++) { for (int i = 0; i < n; i++) { if (i >> j & 1) { a[i] += a[i ^ (1 << j)]; } } } 逆变换 for (int j = 0; j < m; j++) { for (int i = 0; i < n; i++) { if (i >> j & 1) { a[i] -= a[i ^ (1 << j)]; } } } 有的人习惯倒序j,倒序i,其实无所谓 子集和变换,还有可能是0的位置加等于1的位置 for (int j = 0; j < m; i++) { for (int i = 0; i < n; i++) { if (i >> j & 1) { a[i ^ (1 << j)] += a[i]; } } } for (int j = 0; j < m; i++) { for (int i = 0; i < n; i++) { if (i >> j & 1) { a[i ^ (1 << j)] -= a[i]; } } }
a 0~7
b 0~7
或卷积
c[6] = ?
a[i] * b[j] (i | j) == 6
6是0的位置,i和j必须是0
6是1的位置,i和j至少一个是1
先解决第一个条件,第二个条件用容斥解决
对a和b做子集和变换得到A和B
A[6] = (a[0] + a[2] + a[4] + a[6])
B[6] = (b[0] + b[2] + b[4] + b[6])
A[6] * B[6] = ……16项…… 都满足 6是0的位置,i和j必须是0
其中有一些不满足 6是1的位置,i和j至少一个是1
做子集和变换逆变换
C[6] = A[6] * B[6]
c[6] = C[6] - C[4] - C[2] + C[0]
c[6] = sum(a[i] * b[j]) (i | j) == 6
或卷积
a = 2 4 6 8
b = 1 3 5 7
A = 2 6 8 20
B = 1 4 6 16
C = 2 24 48 320
c = 2 22 46 250
与卷积
a = 2 4 6 8
b = 1 3 5 7
A = 20 12 14 8
B = 16 10 12 7
C = 320 120 168 56
c = 88 64 112 56