数据结构 分块 & 莫队
分块
一种优化暴力的思想。
通常是将原数据划分成适当块(一般为 \(\sqrt{n}\)),对每块数据进行预处理,进而达到比暴力更优的时间复杂度。
划分
确定块长后,一般需要开两个数组存储每一块的右边界与原数据所属块序号,更加方便后续操作。
int sq=sqrt(n);
for(int i=1;i<=sq;i++) ed[i]=n/sq*i;
ed[sq]=n;// 将剩下的都放到最后一个块内
for(int i=1;i<=n;i++)
for(int j=ed[i-1]+1;j<=ed[i];j++)
bl[j]=i;
区间查询
查询某区间 \(\left[L,R\right]\) 内信息,主要分为两种情况:
- 查询的区间位于一个块内,直接暴力即可,最坏复杂度为 \(sq\);
- 查询的区间跨越多个块,分为三部分求解:\(\left[L,ed_{bl_{L}}\right]\),中间完整块统计,和最后 \(\left[ed_{bl_R-1},R\right]\),最坏复杂度为 \(\frac{n}{sq}+sq\) 。
在通常情况下取 \(sq=\sqrt{n}\) 时,单次查询复杂度最坏均为 \(\sqrt{n}\)。
以区间求和为例,存在区间修改,提前处理每块和,代码如下:
int Query(int l,int r)
{
int ans=0;
if(bl[l]==bl[r])
{
for(int i=l;i<=r;i++) ans+=a[i]+lazy[bl[i]];
return ans;
}
for(int i=l;bl[i]==bl[l];i++) ans+=a[i]+lazy[bl[i]];
for(int i=bl[l]+1;i<bl[r];i++) ans+=sum[i];
for(int i=r;bl[i]==bl[r];i--) ans+=a[i]+lazy[bl[i]];
return ans;
}
区间修改
情况同查询,分为两种:
- 在同一块内,直接暴力修改;
- 不在同一块内,对于开头结尾两不完整的块暴力修改,中间完整块用 lazy 标记。
复杂度仍然为 \(\sqrt{n}\)。
代码如下:
void modify(int l,int r,int k)
{
if(bl[l]==bl[r])
{
for(int i=l;i<=r;i++) a[i]+=k,sum[bl[i]]+=k;
return;
}
for(int i=l;bl[i]==bl[l];i++) a[i]+=k,sum[bl[i]]+=k;
for(int i=bl[l]+1;i<bl[r];i++) lazy[i]+=k,sum[i]+=k*sq;
for(int i=r;bl[i]==bl[r];i--) a[i]+=k,sum[bl[i]]+=k;
}
莫队
一种离线算法,基于分块思想实现。
使用莫队的前提是,对于区间查询问题,可以以 \(\mathcal{O(1)}\) 的复杂度从已知区间 \(\left[l,r\right]\) 的答案得出 \(\left[l-1,r\right]\),\(\left[l+1,r\right]\),\(\left[l,r-1\right]\),\(\left[l,r+1\right]\) 的答案,那么可以在 \(\mathcal{O(n\sqrt{n})}\) 的复杂度内求出所有询问的答案。
询问预处理
要想使得每一步操作均尽可能的有效,即不进行大幅度的冗余操作,我们需要将乱序的询问提前进行排序。
通常情况下,排序标准为以左边界所属块为第一关键字,右边界为第二关键字进行升序排序,其最优性证明见下。
在一些极特殊情况,我们还可以通过奇偶化排序等方式进一步优化复杂度,通常情况下不必须使用。
复杂度分析
摘自 OI-wiki。
实现
HH 的项链(这道题 \(10^6\) 的数据过根号确实很极限,这里引用只是因为更简单更贴合莫队的思想)
区间求不同种类数,询问预处理就不放了,只放重要部分:
void add(int x)
{
cnt[a[x]]++;
if(cnt[a[x]]==1) ans++;
}
void del(int x)
{
cnt[a[x]]--;
if(!cnt[a[x]]) ans--;
}
int main()
{
/*code*/
for(int i=1;i<=m;i++)
{
while(l<q[i].l) del(l++);
while(l>q[i].l) add(--l);
while(r>q[i].r) del(r--);
while(r<q[i].r) add(++r);
answer[q[i].id]=ans;
}
for(int i=1;i<=m;i++) printf("%d\n",answer[i]);
return 0;
}
回滚莫队
莫队的一种扩展形式,适用于增加或删除操作其一不好维护的情况,主要分为不增加莫队和不删除莫队。
以不删除莫队举例,典型例题是求某一段区间内出现最多次数的数的数量。
思想
首先对原序列分块,将询问排序。
记录一个变量 \(las\),存储上一个询问左边界所在块,若与当前不同则将 \(l\) 指针移至当前询问左边界所在块右边界后一个,\(r\) 指针移至当前询问左边界所在块右边界,保证当前为空集。
若在同一块内则暴力求解,求解后还原。
否则先将 \(r\) 向右跳至询问右边界,同时更新计数数组和答案。
之后新建一个指针 \(l_1\),初始值为 \(l\),并记录此时答案 tmp;然后向左移动指针 \(l_1\) 至询问左边界,同时更新计数数组和答案,此时得到当前询问的答案,记录;最后将 \(l_1\) 指针右移回到 \(l\) 的位置,此时只更新计数数组不更新答案,最后给答案赋值为 tmp,一次询问操作结束。
注意,第二三步的顺序不可调换,因为在移动查询区间左边界所在块的当此操作即可能为同一块内的查询,我们需要先清空计数数组再求解答案。
实现
以典型例题为例,同样只展示关键部分代码:
void add(int x)
{
cnt[a[x]]++;
if(cnt[a[x]]>answer) answer=cnt[a[x]];
}
void del(int x)
{
cnt[a[x]]--;
}
int main()
{
/*code*/
int l=ed[bl[q[1].l]]+1,r=ed[bl[q[1].l]],las=bl[q[1].l];
for(int i=1;i<=m;i++)
{
if(las!=bl[q[i].l])
{
while(r>ed[bl[q[i].l]]) del(r--);
while(l<ed[nl[q[i].l]]+1) del(l++);
answer=0,las=bl[q[i].l];
}
if(bl[q[i].l]==bl[q[i].r])
{
for(int j=q[i].l;j<=q[i].r;j++) cnt[a[j]]++,ans[q[i].id]=max(ans[q[i].id],cnt[a[j]]);
for(int j=q[i].l;j<=q[i].r;j++) cnt[a[j]]--;
continue;
}
while(r<q[i].r) add(++r);
int l1=l,tmp=answer;
while(l1>q[i].l) add(--l1);
ans[q[i].id]=answer;
while(l1<l) del(l1++);
answer=tmp;
}
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}
末
不同数据结构都有各自优点,也都有共通之处,我们应该学到每个思想真正优势的地方,一些较麻烦的可以通过转换方法来解决(所以没写带修莫队)。
本文代码全部线上手打,有问题请指出,感谢支持。
完结撒花~
数据结构 分块 & 莫队的更多相关文章
- 【BZOJ-3809】Gty的二逼妹子序列 分块 + 莫队算法
3809: Gty的二逼妹子序列 Time Limit: 80 Sec Memory Limit: 28 MBSubmit: 1072 Solved: 292[Submit][Status][Di ...
- 2018.11.07 NOIP训练 L的鞋子(权值分块+莫队)
传送门 乱搞题. 我直接对权值分块+莫队水过了. 不过调了30min30min30min发现ststst表挂了是真的不想说什么233. 代码
- HDU 5145 分块 莫队
给定n个数,q个询问[l,r]区间,每次询问该区间的全排列多少种. 数值都是30000规模 首先考虑计算全排列,由于有同种元素存在,相当于每次在len=r-l+1长度的空格随意放入某种元素即$\bin ...
- bzoj 3585 mex - 线段树 - 分块 - 莫队算法
Description 有一个长度为n的数组{a1,a2,...,an}.m次询问,每次询问一个区间内最小没有出现过的自然数. Input 第一行n,m. 第二行为n个数. 从第三行开始,每行一个询问 ...
- 【BZOJ2038】【2009国家集训队】小Z的袜子(hose) 分块+莫队
Description 作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿.终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命……具体来说,小Z把这N只袜 ...
- BZOJ.3809.Gty的二逼妹子序列(分块 莫队)
题目链接 /* 25832 kb 26964 ms 莫队+树状数组:增加/删除/查询 都是O(logn)的,总时间复杂度O(m*sqrt(n)*logn),卡不过 莫队+分块:这样查询虽然变成了sqr ...
- 分块+莫队||BZOJ3339||BZOJ3585||Luogu4137||Rmq Problem / mex
题面:P4137 Rmq Problem / mex 题解:先莫队排序一波,然后对权值进行分块,找出第一个没有填满的块,直接for一遍找答案. 除了bzoj3339以外,另外两道题Ai范围都是1e9. ...
- 【BZOJ 2120】数颜色【分块/莫队】
题意 给出n个数字和m个操作.操作有两种.1:查询区间[l,r]内不同种类得数字个数.2: 将下标为p得数字修改为v 分析 如果不是修改操作的话,用莫队贼简单就可以水过,但是因为带了修改就有一些麻烦了 ...
- P4396 [AHOI2013]作业 分块+莫队
这个题正解是莫队+树状数组,但是我个人非常不喜欢树状数组这种东西,所以决定用分块来水这个题.直接在莫队维护信息的时候,维护单点同时维护块内信息就行了. 莫队就是这几行核心代码: void add(in ...
- BZOJ 3585: mex(分块+莫队)
传送门 解题思路 首先直接莫队是能被卡的,时间复杂度不对.就考虑按照值域先进行分块再进行莫队,然后统计答案的时候就暴力扫所有的块,直到一个块内元素不满,再暴力扫这个块就行了,时间复杂度O(msqrt( ...
随机推荐
- Docker中部署单机Redis详细教程
1.拉取Redis镜像 # 拉取redis镜像,不指定版本则默认是最新版本 docker pull redis 2.查看镜像 # 列出本地镜像 docker images 3.准备配置文件路径 # 创 ...
- shell中各个括号的用法区别
在 shell 脚本中,[ ].[[ ]].( ).(( )).{ } 和 {{ }} 都有各自特定的用法和区别.下面是对这些结构的详细解释: 1. [ ] (test 命令) [ ] 是 shell ...
- 配置阿里云yum源
CentOS6 rm -f /etc/yum.repos.d/* wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.co ...
- 原生js或者是es中让人厌恶的一些地方
js总体来说,是个不错的语言,最大的好处的是简单. 但这个基于es6的一些js也有一些非常怪异的写法,这是非常令人憎恶的地方. c++总体上也算不错,但为什么不是很受欢迎,因为它把自己搞得太复杂了,复 ...
- Linux安全审计之audit安装与使用
场景 安全最重要的一步是内部安全,如何监控用户的行为是一个永恒不变的话题. audit可以详细监控用户的行为,详细到查看或修改了某个文件.这些都可以在日志中查看到. 安装 小贴士: CentOS默认已 ...
- ClickHouse的物化视图及MySQL表引擎
MySQL表引擎可以与MySQL数据库中的数据表简历映射,并通过SQL向其发起远程查询. MySQL表引擎可以与物化视图结合,来同步更新MySQL数据库中的数据. 语法: CREATE TABLE [ ...
- 03-CSS初步介绍
01 CSS编写规则 1.1 内联样式 <!DOCTYPE html> <html lang="en"> <head> <meta cha ...
- .NET 个人博客-首页排版优化
个人博客-首页排版优化 优化计划 置顶3个且可滚动或切换 推荐改为4个,然后新增历史文章,将推荐的加载更多放入历史文章,按文章发布时间降序排列. 标签功能,可以为文章贴上标签 推荐点赞功能 本篇文章优 ...
- Android发布,全志T507四核A53@1.4GHz工业平台,含税仅168元起!
近年来,Android系统在工业自动化.仪器仪表.医疗.安防等工业领域的使用日趋广泛.为了满足广大工业用户的需求,创龙科技针对全志T507-H工业平台进行了Android系统适配. 创龙科技T507- ...
- Yuno loves sqrt technology I 题解
申明:由于本人卡常技艺不佳,本题解复杂度正确但无法通过 首先分块,然后考虑分开计算贡献维护,看下面一张图: 我们将贡献拆分为 \(ans(A) + ans(B) + ans(C) + ans(AB) ...