枚举子集&高维前缀和学习笔记
枚举子集
首先 \(n\) 位二进制数可以表示一个大小为 \(n\) 的集合的所有子集。接下来的问题均用二进制数展开。
一种暴力的想法是枚举所有数然后判一下是否满足条件,单次时间复杂度 \(O(2^n)\),对所有数做一遍就是 \(O(4^n)\)。
发现有很多枚举是无用的,考虑怎么样让每次枚举出来的都必定是个子集。
对于两个二进制数 \(a,b\),\(a\operatorname{and}b\) 就是 \(a\) 和 \(b\) 的交集。
而将一个二进制数减一会怎么变化?最后一位 \(1\) 变成 \(0\),后面的 \(0\) 全部变成 \(1\)。考虑与上原数,就变成了去掉最低位的 \(1\),将更低位取满。重复这两个操作,相当于每次改变当前最低位的状态,枚举更低位的子集。正确性显然,枚举顺序自然是从大到小。
现在的时间复杂度其实就是子集个数的级别。一个含有 \(i\) 个 \(1\) 的二进制数的子集个数为 \(2^i\),所有二进制数的子集个数加和就是:
将 \(x=1,y=2\) 代入就等于 \((1+2)^n=3^n\)。
当然也可以通过组合意义来阐释,每个位置要么为 \(1\)(该位一定选)要么为 \(0\)(可以选也可以不选)。
高维前缀和
基本是贺 OI Wiki 的
现在有一个 \(U\) 维空间 \(D\),需要对每个点 \(state\) 计算满足所有维度均小于等于 \(state\) 的点的贡献。
令 \(sum_{i,state}\) 表示后 \(U-i\) 维均与 \(state\) 相同的点的贡献,那么初始化 \(sum_{0,state}\) 为与 \(state\) 相同的点的贡献,最终答案为 \(sum_{U,state}\)。
其递推关系是 \(sum_{i,state}=sum_{i-1,state}+sum_{i,state'}\),其中 \(state'\) 为第 \(i\) 维 \(state\) 的前驱(第 \(i\) 维恰好少 \(1\))。时间复杂度为 \(O(D\times|U|)\),\(|U|\) 其实就是每一维大小的乘积。
如果无法直接理解可以慢慢来,我们从二维入手:一个矩阵。
先按行做一遍前缀和,再对行的前缀数组按列做一遍前缀和。容易发现,点 \((x,y)\) 通过一条特定的路径对点 \((x',y')(x\leq x',y\leq y')\) 产生贡献,这条路径就是:
推广到更高维的情况,一维一维地搞,一步一步地递推,都能得到这样一条特定的路径,所以不重不漏。
实际上就是已经递推过的维小于等于的情况已经算进了已经递推过的维等于的情况。