分块,优雅的暴力

2023-07-31 18:07:45 来源:博客园

来看一下一个例题:


(资料图)

  • 现在给出一个长度为 \(N\) 序列 A,定义两个操作如下:

  • 1 l r v,表示从 \(A_l \sim A_r\) 每个数都加上 \(v\)。

  • 2 l r,对 \(A_l \sim A_r\) 求和。

传统的线段树可以很优秀地实现这两个操作,但是需要打 \(lazytag\)。

同时因为线段树(非动态开点)的空间复杂度为 \(O(4N)\),在空间限制或数据范围较大的题目中容易被卡,于是考虑用时间换空间,开发空间复杂度更优的算法。

分块就是这样的算法。

分块算法将整个序列分为若干个长度不超过 \(\sqrt n\) 的区间,并对于每个区间维护两个标记增量标记 \(add\) 和区间和 \(sum\)。其中增量标记 \(add\) 用来记录对于整个块的加法对答案产生的贡献,区间和 \(sum\) 用来记录整个区间内增量标记 \(add\) 之外的答案。

所以区间和查询就较为简单了。对于 \([l,r]\) 区间内的每个块,直接算 \(sum+add*(R_i-L_i+1)\) 计入答案即可,其中 \(L_i\) 和 \(R_i\) 分别表示第 \(i\) 个区间的左右端点。而对于区间两端不被整个分块覆盖的位置直接暴力统计即可。记 \(l,r\) 所在区间编号分别为 \(p,q\),则两端的查询答案即为

\[\sum_{i=l}^{R_p}a_i+add_p\times(R_p-l+1)+\sum_{i=L_q}^{r}a_i+add_q\times(r-L_q+1)\]

于是将中间部分的答案和两端的答案直接合并就可以得到查询操作的答案了。

PS:如果 \(l,r\) 属于同一区间,则直接暴力统计即可。

inline int query(int l, int r) {    int res = 0, p = pos[l], q = pos[r];    if(p == q) {        for(int i = l; i <= r; i++) {            res += a[i] + add[i];        }        return res;    }    else {        for(int i = p + 1; i < q; i++) {            res += sum[i] + add[i] * (R[i] - L[i] + 1);        }        for(int i = l; i <= R[p]; i++) {            res += a[i];        }        res += add[p] * (R[p] - l + 1);        for(int i = L[q]; i <= r; i++) {            res += a[i];        }        res += add[q] * (r - L[q] + 1);        return res;    }}

如此暴力的做法复杂度是如何保证的呢?

观察到由于分块时每个区间长度均不超过 \(\sqrt N\),于是中间统计部分最多不超过 \(\lceil \sqrt N \rceil\) 个区间,而两端暴力统计的次数总的不超过 \(2\sqrt N\),所以整个查询的复杂度可以近似视作 \(O(\sqrt N)\)。

对于修改操作可以用相同的思想,每个完整区间直接加在增量标记 \(add\) 上,不完整区间则暴力往原序列 \(A_i\) 和区间和 \(sum\) 上加。总复杂度用相同的方法分析可以得到也为 \(O(\sqrt N)\)。

inline void change(int l, int r, int v) {    int p = pos[l], q = pos[r];    if(p == q) {        for(int i = l; i <= r; i++) {            a[i] += v;        }        sum[p] += (r - l + 1) * v;    }    else {        for(int i = p + 1; i < q; i++) {            add[i] += v;        }        for(int i = l; i <= R[p]; i++) {            a[i] += v;        }        sum[p] += (R[p] - l + 1) * v;        for(int i = L[q]; i <= r; i++) {            a[i] += v;        }        sum[q] += (r - L[q] + 1) * v;    }}

算法的预处理部分要处理的为每个区间的左右端点 \(L_i,R_i\),每个位置 \(i\) 所对应的区间 \(pos_i\),和原序列的区间和 \(sum_j\),总复杂度为 \(O(N\sqrt N)\)

对于最后一个长度不满 \(\sqrt N\) 的区间直接处理即可。

t = sqrt(n);    for(int i = 1; i <= t; i++) {        L[i] = (i - 1) * sqrt(n) + 1;        R[i] = i * sqrt(n);    }    if(R[t] < n) {        t++;        L[t] = R[t - 1] + 1;        R[t] = n;    }    for(int i = 1; i <= t; i++) {        for(int j = L[i]; j <= R[i]; j++) {            pos[j] = i;            sum[i] += a[j];        }    }

若序列长度为 \(N\),总操作数为 \(Q\),则由上述分析可得分块算法的时间复杂度上界为 \(O((N+Q)\sqrt N)\),空间复杂度为 \(O(N)\)。

在处理区间问题时,通常有树状数组、线段树和分块三种方法,三种方法各有优劣。

  • 树状数组效率最高,代码最短;但不易扩展,不够直观,调试难度大。

  • 线段树效率较高,扩展性好;但代码较长,且直观性一般,调试难度较大。

  • 分块算法最为通用且直观,且代码较短;但效率一般。在复杂度允许,且正确性有保证的情况下,写分块的考场收益一般最大。

分块还可以较快地求区间不超过 \(k\) 的数的个数。(但让我先鸽一鸽

标签

知识 双簧是什么意思

双簧是曲艺的一种。一人表演动作,一人藏在身后说或唱,互相配合。比喻双方串通的活动,由一方出面,另...

2022-11-25 16:18:28

全国新能源汽车下乡活动在昆山启动 将发放500万元“红包”

6月17日,由中国汽车工业协会、省工信厅、省农业农村厅、省商务厅、省发改委、苏州市政府、新华日报社、...

2022-06-20 16:48:35

安阳本土确诊病例上升至26例

  中新网安阳1月10日电 (杨大勇)10日,河南省安阳市召开新冠肺炎疫情防控工作第二场新闻发布会通报称...

2022-01-10 15:22:56

3次推迟婚期 满洲里抗疫民警兑现承诺:“我回来娶你了!”

  (抗击新冠肺炎)3次推迟婚期 满洲里抗疫民警兑现承诺:“我回来娶你了!”  中新网呼伦贝尔1月10...

2022-01-10 15:22:56

上海公安民警在岗位上迎接2022年“中国人民警察节”

  中新网上海1月10日电(记者 李姝徵)“我志愿成为中华人民共和国人民警察,献身于崇高的人民公安事业...

2022-01-10 15:22:55

郑州核酸检测为中小学生开辟“绿色通道”

  (抗击新冠肺炎)郑州核酸检测为中小学生开辟“绿色通道”  中新网郑州1月10日电(杨大勇)“学生不用...

2022-01-10 15:22:55

反扒便衣警察“小曹”:藏在人海中的隐形“守护者”

  小曹说,他现在理解了师父当年如何历练出一副“火眼”,碰见的贼多了,案子经手的多了,自然就有了...

2022-01-10 15:22:54

哥哥移植肾脏给病重弟弟 已在上海顺利康复

  中新社上海1月10日电 (陈静 王根华)在上海武警服役的弟弟被尿毒症击倒,哥哥义无反顾地捐献出自...

2022-01-10 15:22:54

网友与人裸聊被敲诈10万余元 被告人获刑5年

  中新网长春1月10日电 (谭伟旗)当下,新型网络诈骗案件已较为普遍,由于网络上身份的不确定性、语言...

2022-01-10 15:22:53

1月10日起天津市暂停开展旅行社旅游业务活动

  中新网1月10日电 据天津市文旅局官网消息,天津市文化和旅游局10日发布紧急通知称,即日起,天津市...

2022-01-10 15:22:53
x 广告
x 广告

Copyright  2015-2022 世界粮油网版权所有  备案号:琼ICP备2022009675号-1   联系邮箱:435 227 67@qq.com