题目描述
给定两个长度为 2n 的序列 a0,a1,⋯,a2n−1 和 b0,b1,⋯,b2n−1,你需要求出一个序列 c0,c1,⋯,c2n−1,其中 ck 满足:
ck=∑i&j=0 i∣j=kaibj
其中 ∣ 表示按位或,&表示按位与。
答案对 109+9 取模。
过程
在没有条件 i&j=0 时,这就是一个普通的 FWT 卷积,考虑如何消掉这个条件。
若有一个序列 f,我们重新定义它的值为 f′,p(x) 表示 x 二进制表示下 1 的个数。
f′j,i{fi,p(i)=j,0,otherwise.
于是发现当 p(i)+p(j)=p(k) 且 i|j=k 时,一定满足 i&j=0,所以我们只需在 FWT 的基础上多增加一维即可。
时间复杂度 O(n22n) 。
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 21, mod = 1e9 + 9;
int a[N][1 << N], b[N][1 << N], c[N][1 << N];
int p(int x) {return __builtin_popcount(x);}
void OR(int *a, int n, int x) {
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 ++ )
a[i + j + k] = ((a[i + j + k] + a[i + j] * x) % mod + mod) % mod;
}
int main() {
int n;
scanf("%d", &n);
for (int i = 0; i < (1 << n); i ++ ) scanf("%d", &a[p(i)][i]);
for (int i = 0; i < (1 << n); i ++ ) scanf("%d", &b[p(i)][i]);
for (int i = 0; i <= n; i ++ ) OR(a[i], 1 << n, 1), OR(b[i], 1 << n, 1);
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] = (c[i][k] + 1ll * a[j][k] * b[i - j][k] % mod) % mod;
OR(c[i], 1 << n, -1);
}
for (int i = 0; i < (1 << n); i ++ ) printf("%d ", c[p(i)][i]);
return 0;
}