高维前缀和&子集枚举
参考资料
子集枚举
$O(4^{n})$ 到 $O(3^{n})$ 的一个优化1
2
3
4
5
6
7
8for(int s=0;s<(1<<n);s++)
{
for(int i=s;;i=(i-1)&s)
{
//code here...
if(!i) break;
}
}
首先,对于一个二进制下全是1的数不断减1可以遍历他的所有子集,i-1会把末尾的0都变成1,而&s则保证了原先s上是0的位仍是0,整个循环相当于一个忽略s中是0的位而不断减1的过程。
高维前缀和
一维前缀和
1 | for(int i=1;i<=n;i++) |
二维前缀和
1 | for(int i=1;i<=n;i++) |
相当于先对列求和,在对行求和
三维前缀和
1 | for(int i=1;i<=n;i++) |
可以画个图理解下,,,
N维前缀和?
仿照上面的写法,$n$ 个 $n$ 重循环,每次对一个维度求和即可。
状压 & 与子集枚举的联系
对于一个 $n$ 维数组,设它所有维度上的最大值为 $k$,我们可以把 $n$ 维上的每一个点压成一个 $k+1$ 进制的数,求高维前缀和时用上面的方法即可。
二进制中的子集关系($i \subset j$)可以看作是每一位上的偏序关系,而 $n$ 维前缀和刚好是满足这种偏序关系的。
所以,对于类似于这类问题:
都可以使用高位前缀和的技巧将复杂度由 $O(3^{n})$ 降到 $O(n*2^{n})$
代码也很好写:
1 | for(int i=1;i<=n;i++) |
例题
1.CF1221G Graph And Numbers
题解先坑着,,,
1 | LL n,m,c; |
2.CF449D Jzzhu and Numbers
题意:
给定数列 $a_{1}…a_{n}$,计算从中取一段子序列使其 $And$ 起来的和为 $0$ 的方案数。
SOL:
考虑容斥计算不合法的方案数,$And$ 后有一位为1的方案数 - 有两位为1的方案数…
然后考虑如何计算 $n$ 位为1的方案数,设 $f[s]$ 等于满足下面条件的 $a_{i}$ 的个数:
- $s$ 为1的二进制位上 $a_{i}$ 也为1
- 其他位上任意
可以发现这是个超集(取反后的子集),求出 $f$ 数组后就可以愉快的容斥辣
1 | int lowbit(int x){return x&-x;} |
3.CF1234F Yet Another Substring Reverse
题意:
给定一个字符串,你可以翻转一段子串(或者不),之后求原字符串中满足字符两两不同的子串的最长长度。
字符只有 ‘a’~’t’ (20个)
SOL:
首先考虑一下执行翻转操作后答案长啥样,一定是一段没翻转拼上一段翻转过的,所以可以将问题转化为 选两个子串拼起来得到一个新串且满足字符两两不同,求这个新串最长是多少。
考虑状压,$s$二进制位上的1/0代表这个字符出现/没出现。若 $i$ 想和 $s$ 拼到一起的话, $s$ 是1的位置上 $i$ 不能是1,而其他位上没有限制,用高维前缀和处理出满足上述条件的子串的最长长度 $f[i]$ 之后暴力枚举即可。
1 | int n; |
题外话:有生之年终于AK了DIV3