算法介绍——高维前缀和

引入

我们都知道二维前缀和有这么一个容斥的写法:

for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
}
}

那换成三维前缀和,就有如下容斥代码:

\[s[i][j][k]=a[i][j][k]+s[i-1][j][k]+s[i][j-1][k]+s[i][j][k-1]-s[i-1][j-1][k]-s[i-1][j][k-1]-
s[i][j-1][k-1]+s[i-1][j-1][k-1]
\]

非常的繁琐,于是就诞生了如下二维前缀和的写法:

for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
s[i][j]=s[i][j-1]+a[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
s[i][j]+=s[i-1][j];
}
}

其实就是先统计列的前缀和,再统计行的前缀和。

那三维前缀和只需要三遍 forforfor 就搞定了,明显简单很多。

这就启发我们对于更高维度的前缀和,同样只需要做 \(n\)遍\(n\)层 for 循环

正文

对于上面的 \(n\)遍\(n\)层for循环 的高维前缀和的代码,其复杂度显然不是我们能接受的,但是当每一维很小时就可以进行状态压缩

最常见的就是每一维的大小为 \(2\) ,此时对于\(L\) 维数组就可以用一个长度为 \(L\) 的二进制数表示其中一个位置。

for(int i=0;i<L;i++){
for(int j=0;j<(1<<L);j++){
if(j>>i&1){
f[j]+=f[j^(1<<i)];
}
}
}

时间复杂度\(O(L\times2^L)\)

子集求和

对于一个二进制数 \(j\) ,如果另一个二进制数 \(i\) 满足,\(i \operatorname{and} j=i\) , 就说 \(j\) 包含 \(i\) , 即 \(i\) 是 \(j\) 的子集。

那上面的代码相当于对每一个 \(j\) 加上它的所有子集,我们称它为 子集求和

超集求和

对于一个二进制数 \(j\) ,如果另一个二进制数 \(i\) 满足,\(i \operatorname{or} j=i\) , 此时 \(i\) 包含 \(j\) , 即 \(j\) 是 \(i\) 的子集,此时我们称 \(i\) 是 \(j\) 的超集

与子集求和类似的,我们有如下 超集求和 代码:

for(int i=0;i<L;i++){
for(int j=0;j<(1<<L);j++){
if(!(j>>i&1)){
f[j]+=f[j^(1<<i)];
}
}
}

应用

高维前缀和是计数的常用技巧,因此也被称为SOSDP

其他

高维前缀和有时会配合Lucas定理使用,参见Lucas定理入门


例题

Compatible Numbers

\(a \operatorname{and} b = 0\) 等价于 \(a\) 是 \(b\) 的补集的子集,因为题目要求输出任意一个答案,所以只要把上述子集求和的代码中得加操作改成赋值操作即可。

#include<bits/stdc++.h>
using namespace std;
const int N=4e6+5;
inline int read(){
int w = 1, s = 0;
char c = getchar();
for (; c < '0' || c > '9'; w *= (c == '-') ? -1 : 1, c = getchar());
for (; c >= '0' && c <= '9'; s = 10 * s + (c - '0'), c = getchar());
return s * w;
}
int n;
int a[N];
int f[1<<22];
signed main(){
n=read();
for(int i=1;i<=n;i++){
a[i]=read();
f[a[i]]=a[i];
}
for(int i=0;i<=21;i++) {
for(int j=0;j<(1<<22);j++) {
if((j&(1<<i))&&f[j^(1<<i)]) f[j]=f[j^(1<<i)];
}
}
for(int i=1;i<=n;i++){
int b=((1<<22)-1)^a[i]; //计算补集
if(f[b]) printf("%d ",f[b]);
else printf("%d ",-1);
}
return 0;
}

[ARC137D] Prefix XORs

设 \(A_{i,j}\) 表示第 \(j\) 次操作后 \(A_i\) 的值,根据常识或手推可以知道:

\[A_{n,k}=\oplus^n_{i=1} C^{k}_{n-i+k} \cdot A_{i,0}
\]

因为偶数个 \(A_i\) 异或起来的的结果为\(0\),所以 \(A_{i,0}\) 对 \(A_{n,k}\) 有贡献当且仅当 \(C^{k}_{n-i+k}\) 为奇数,即 $ C^{k}_{n-i+k} \equiv 1 \pmod 2 $,根据 Lucas 定理可知,此时 \(k\) 是 \(n-i+k\) 的子集,即 $k \operatorname{and} (n-i) = 0 $,用高维前缀和预处理即可

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
inline int read(){
int w = 1, s = 0;
char c = getchar();
for (; c < '0' || c > '9'; w *= (c == '-') ? -1 : 1, c = getchar());
for (; c >= '0' && c <= '9'; s = 10 * s + (c - '0'), c = getchar());
return s * w;
}
int n,m;
int a[N];
int f[1<<20];
signed main(){
n=read(),m=read();
for(int i=1;i<=n;i++){
a[i]=read();
}
for(int j=0;j<n;j++) f[j]=a[n-j];
for(int i=0;i<=19;i++){
for(int j=0;j<(1<<20);j++){
if(j>>i&1){
f[j]^=f[j^(1<<i)];
}
}
}
for(int k=0;k<m;k++){ //k从0开始
printf("%d ",f[((1<<20)-1)^k]); //求补集的答案
}
return 0;
}

高维前缀和 (SOSDP)的更多相关文章

  1. BZOJ.5092.[Lydsy1711月赛]分割序列(高维前缀和)

    题目链接 \(Description\) \(Solution\) 首先处理\(a_i\)的前缀异或和\(s_i\).那么在对于序列\(a_1,...,a_n\),在\(i\)位置处分开的价值为:\( ...

  2. HDU.5765.Bonds(DP 高维前缀和)

    题目链接 \(Description\) 给定一张\(n\)个点\(m\)条边的无向图.定义割集\(E\)为去掉\(E\)后使得图不连通的边集.定义一个bond为一个极小割集(即bond中边的任意一个 ...

  3. SPOJ.TLE - Time Limit Exceeded(DP 高维前缀和)

    题目链接 \(Description\) 给定长为\(n\)的数组\(c_i\)和\(m\),求长为\(n\)的序列\(a_i\)个数,满足:\(c_i\not\mid a_i,\quad a_i\& ...

  4. LOJ2542 PKUWC2018 随机游走 min-max容斥、树上高斯消元、高维前缀和、期望

    传送门 那么除了D1T3,PKUWC2018就更完了(斗地主这种全场0分的题怎么会做啊) 发现我们要求的是所有点中到达时间的最大值的期望,\(n\)又很小,考虑min-max容斥 那么我们要求从\(x ...

  5. Luogu3175 HAOI2015 按位或 min-max容斥、高维前缀和、期望

    传送门 套路题 看到\(n \leq 20\),又看到我们求的是最后出现的位置出现的时间的期望,也就是集合中最大值的期望,考虑min-max容斥. 由\(E(max(S)) = \sum\limits ...

  6. BZOJ5092:[Lydsy1711月赛]分割序列(贪心,高维前缀和)

    Description 对于一个长度为n的非负整数序列b_1,b_2,...,b_n,定义这个序列的能量为:f(b)=max{i=0,1,...,n}((b_1 xor b_2 xor...xor b ...

  7. HihoCoder - 1496:寻找最大值(高维前缀和||手动求子集)

    描述 给定N个数A1, A2, A3, ... AN,小Ho想从中找到两个数Ai和Aj(i ≠ j)使得乘积Ai × Aj × (Ai AND Aj)最大.其中AND是按位与操作. 小Ho当然知道怎么 ...

  8. BZOJ:5092 [Lydsy1711月赛]分割序列(贪心&高维前缀和)

    Description 对于一个长度为n的非负整数序列b_1,b_2,...,b_n,定义这个序列的能量为:f(b)=max{i=0,1,...,n}((b_1 xor b _2 xor...xor ...

  9. BZOJ4036:按位或 (min_max容斥&高维前缀和)

    Description 刚开始你有一个数字0,每一秒钟你会随机选择一个[0,2^n-1]的数字,与你手上的数字进行或(c++,c的|,pascal 的or)操作.选择数字i的概率是p[i].保证0&l ...

  10. cf449D. Jzzhu and Numbers(容斥原理 高维前缀和)

    题意 题目链接 给出\(n\)个数,问任意选几个数,它们\(\&\)起来等于\(0\)的方案数 Sol 正解居然是容斥原理Orz,然而本蒟蒻完全想不到.. 考虑每一种方案 答案=任意一种方案 ...

随机推荐

  1. 解码技术债:AI代码助手与智能体的革新之道

    技术债 技术债可能来源于多种原因,比如时间压力.资源限制.技术选型不当等.它可以表现为代码中的临时性修补.未能彻底解决的设计问题.缺乏文档或测试覆盖等.虽然技术债可以帮助快速推进项目进度,但长期来看, ...

  2. Mysql 分表分库的策略

    为什么要分表? 当一张的数据达到几百万时,你查询一次所花的时间会变多,如果有联合查询的话,有可能会死在那儿了. 分表的目的就在于此,减小数据库的负担,缩短查询时间. 日常开发中我们经常会遇到大表的情况 ...

  3. 教你基于MindSpore用DCGAN生成漫画头像

    本文分享自华为云社区<[昇思25天学习打卡营打卡指南-第二十天]DCGAN生成漫画头像>,作者:JeffDing. DCGAN生成漫画头像 在下面的教程中,我们将通过示例代码说明DCGAN ...

  4. 通俗讲解promise

        JavaScript 中的 Promise 是一种特殊的对象,它用于解决异步编程中的复杂性问题,特别是回调的问题.我们可以把它比喻成现实生活中的一个"承诺": 想象一下,你 ...

  5. EF6/EFCore Code-First Timestamp SQL Server

    EF 6和EF Core都包含TimeStamp数据注解特性.它只能用在实体的byte数组类型的属性上,并且只能用在一个byte数组类型的属性上.然后在数据库中,创建timestamp数据类型的列,在 ...

  6. 整数-笔记C

    实际情况也确实如此,C语言并没有严格规定 short.int.long 的长度,只做了宽泛的限制: short 至少占用 2 个字节. int 建议为一个机器字长.32 位环境下机器字长为 4 字节, ...

  7. for循环以及常用的遍历(迭代)用法

    for循环以及常用的遍历(迭代)用法   概念:(概念才是高楼的地基!) for循环是一个计次循环,一般运用在循环次数已知的情况下.通常适用于枚举或遍历序列,以及迭代序列中的元素. 注意*:迭代变量用 ...

  8. XXL-JOB初见

    XXL-JOB是轻量级分布式任务调度平台 port:8088 初始账号:admin/123456 主要有调度中心.执行器.任务 执行流程: 1.执行器向调度中心上报任务 2.调度中心为执行器分配任务 ...

  9. 【FastDFS】06 SpringBoot实现上传

    创建SpringBoot工程: 再导入所需要的依赖: <dependency> <groupId>net.oschina.zcx7878</groupId> < ...

  10. python高性能计算:cython使用openmp并行 —— 报错:undefined symbol: omp_get_thread_num

    test.pyx文件: from cython.parallel cimport parallel from openmp cimport omp_get_thread_num cpdef void ...