【luogu P6097】【模板】子集卷积(FWT)


【模板】子集卷积

题目链接:luogu P6097

题目大意

给你两个长度为 2^n 的序列 a,b,要你求一个序列 c 满足:
c[k]=sum(i&j=0,i|j=k)a[i]b[j]

思路

\(c_k=\sum\limits_{i\&j=0,i|j=k}a_ib_j\)
首先考虑只有一个条件 \(i|j=k\):那就是一个或的卷积,直接用 FWT。
考虑处理 \(i\&j=0\)
发现就是两边不能有一个位置都是 \(1\),或者说我们设 \(num1_i\)\(i\) 二进制 \(1\) 的个数,就会有 \(num1_i+num1_j=num1_{i| j}\)

那我们考虑开 \(n\) 个 FWT,然后每个位置 \(i\) 的值一开始放在 \(num1_i\) 的位置。
然后跑 FWT 之后我们就 DP:\(f_{k,S}=\sum\limits_{i+j=k}f_{i,S}f_{j,S}\)
然后再 IFWT 回去就是答案了(注意第 \(i\) 个位置的答案也是放在 \(num1_i\) 的位置)。

代码

#include
#define mo 1000000009

using namespace std;

int n, f[21][1 << 20], g[21][1 << 20], h[21][1 << 20];

int num1(int x) {
	int re = 0;
	while (x) {re++; x -= x & (-x);}
	return re;
}

int jia(int x, int y) {return x + y >= mo ? x + y - mo : x + y;}
int jian(int x, int y) {return x < y ? x - y + mo : x - y;}
int cheng(int x, int y) {return 1ll * x * y % mo;}

void FWT(int *f, int limit, int op) {
	for (int mid = 1; mid < limit; mid <<= 1) {
		for (int R = mid << 1, j = 0; j < limit; j += R) {
			for (int k = 0; k < mid; k++) {
				int x = f[j | k], y = f[j | mid | k];
				f[j | k] = x; f[j | mid | k] = jia(cheng(x, op), y);
			}
		}
	}
}

int main() {
	scanf("%d", &n);
	for (int i = 0; i < (1 << n); i++) scanf("%d", &f[num1(i)][i]);
	for (int i = 0; i < (1 << n); i++) scanf("%d", &g[num1(i)][i]);
	
	for (int i = 0; i <= n; i++) {
		FWT(f[i], 1 << n, 1); FWT(g[i], 1 << n, 1);
	}
	for (int i = 0; i <= n; i++) {
		for (int j = 0; i + j <= n; j++)
			for (int S = 0; S < (1 << n); S++) {
				h[i + j][S] = jia(h[i + j][S], cheng(f[i][S], g[j][S]));
			}
	}
	for (int i = 0; i <= n; i++) {
		FWT(h[i], 1 << n, mo - 1);
	}
	
	for (int i = 0; i < (1 << n); i++) printf("%d ", h[num1(i)][i]);
	
	return 0;
}

相关