Luogu 6097 【模板】子集卷积

upd 2023/3/16:更改了时间复杂度的错误。


其实是暴力。

因为这是模板题,所以模板的前置知识也要讲。

  • 前置知识:FWT 计算或卷积。

这里只需要掌握快速计算或卷积的方法,所以内容较少。如果向了解更多(比如异或卷积)的话可以去 P4717 看看。

例题:给定长度为 2n 的序列 a,b,求 ck=i|j=kaibj 序列中每一项的值。我们需要一个 O(n2n) 的解法。

考虑根据 a,b 构造两个序列 A,B,若 aA,bB 均为 O(n2n) 并且这个过程可逆,令 Ci=Ai×Bi,若 C 能还原回 c,那么我们就可以 O(n2n) 计算 c

在这里,我们令 Ai=j|i=iaj(也就是 ji 的子集)。那么有 Ci=(j|k)|i=iajbk

那么有:

Ai×Bi=(j|i=iaj)(j|i=ibj)=j|i=i,k|i=iajbk=(j|k)|i=iajbk=Ci

也就是说我们证明了这个 A,B 是的确能映射到一个正确的 C 的。现在考虑如何快速求 Ai=j|iaj

显然可以从低到高枚举每个二进制位,然后当前位为 0 的是右边对应位置为 1 的子集,从左依次贡献到右即可。

Cc 相当于求一个逆过程,右边依次减去左边的贡献即可。

参考了这里的代码。

void fwt(int *s, int op) {
	op = (op + mod) % mod;
	for (int o = 2, k = 1; o <= S + 1; o <<= 1, k <<= 1) 
		for (int i = 0; i <= S; i += o)
			for (int j = 0; j < k; j++)
				(s[i + j + k] += 1ll * s[i + j] * op % mod) %= mod;
}
  • 回到原题

你发现这东西就多加了一个限制 i&j=0,也就是说 i,j 无交。

考虑一个充要条件,i&j=0 并且 i|j=k 其实就相当于 |i|+|j|=|k| 并且 i|j=k|i| 表示 i 集合的大小,即 i1 的个数。

所以可以预处理 fi,j 表示满足 |j|=iaj 的值,gi,jb 同理。

那么 ck=i=0nj=02n1j|l=kfi,jgki,l

hi=k=0nfkgik 表示进行或卷积,那么 ci=h|i|,i

FWT 预处理每个 fkgk 的子集和,枚举这个 i,k 求出 h 的子集和,然后做逆的 FWT 就做完了。复杂度 O(n22n)

const int maxs = (1 << 20) + 100;
const int mod = 1e9 + 9;
int n, S, a[maxs], b[maxs], c[21][maxs], f[21][maxs], g[21][maxs];
int p[maxs];

void fwt(int *s, int op) {
	op = (op + mod) % mod;
	for (int o = 2, k = 1; o <= S + 1; o <<= 1, k <<= 1) 
		for (int i = 0; i <= S; i += o)
			for (int j = 0; j < k; j++)
				(s[i + j + k] += 1ll * s[i + j] * op % mod) %= mod;
}

int main() {
	n = read(), S = (1 << n) - 1;
	for (int i = 1; i <= S; i++) p[i] = p[i >> 1] + (i & 1);
    for (int i = 0; i <= S; i++) f[p[i]][i] = read();
    for (int i = 0; i <= S; i++) g[p[i]][i] = read();
	for (int i = 0; i <= n; i++) fwt(f[i], 1), fwt(g[i], 1);
	for (int i = 0; i <= n; i++)
		for (int j = 0; j <= i; j++)
			for (int k = 0; k <= S; k++)
				(c[i][k] += 1ll * f[j][k] * g[i - j][k] % mod) %= mod;
	for (int i = 0; i <= n; i++) fwt(c[i], -1);
	for (int i = 0; i <= S; i++) write(c[p[i]][i]), pc(' ');
	return 0;
}
posted @   Arghariza  阅读(12)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示
主题色彩