数据结构 分块 & 莫队
Upd on 8.18:带修莫队,二维莫队算法现已整理发布,点击跳转。
分块
一种优化暴力的思想。
通常是将原数据划分成适当块(一般为
划分
确定块长后,一般需要开两个数组存储每一块的右边界与原数据所属块序号,更加方便后续操作。
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;
区间查询
查询某区间
- 查询的区间位于一个块内,直接暴力即可,最坏复杂度为
; - 查询的区间跨越多个块,分为三部分求解:
,中间完整块统计,和最后 ,最坏复杂度为 。
在通常情况下取
以区间求和为例,存在区间修改,提前处理每块和,代码如下:
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 标记。
复杂度仍然为
代码如下:
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;
}
莫队
一种离线算法,基于分块思想实现。
使用莫队的前提是,对于区间查询问题,可以以
询问预处理
要想使得每一步操作均尽可能的有效,即不进行大幅度的冗余操作,我们需要将乱序的询问提前进行排序。
通常情况下,排序标准为以左边界所属块为第一关键字,右边界为第二关键字进行升序排序,其最优性证明见下。
在一些极特殊情况,我们还可以通过奇偶化排序等方式进一步优化复杂度,通常情况下不必须使用。
复杂度分析
摘自 OI-wiki。
实现
HH 的项链(这道题
区间求不同种类数,询问预处理就不放了,只放重要部分:
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;
}
回滚莫队
莫队的一种扩展形式,适用于增加或删除操作其一不好维护的情况,主要分为不增加莫队和不删除莫队。
以不删除莫队举例,典型例题是求某一段区间内出现最多次数的数的数量。
思想
首先对原序列分块,将询问排序。
记录一个变量
若在同一块内则暴力求解,求解后还原。
否则先将
之后新建一个指针
注意,第二三步的顺序不可调换,因为在移动查询区间左边界所在块的当此操作即可能为同一块内的查询,我们需要先清空计数数组再求解答案。
实现
以典型例题为例,同样只展示关键部分代码:
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(r<ed[bl[q[i].l]]) add(++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;
}
末
不同数据结构都有各自优点,也都有共通之处,我们应该学到每个思想真正优势的地方,一些较麻烦的可以通过转换方法来解决(所以没写带修莫队)。
本文代码全部线上手打,有问题请指出,感谢支持。
完结撒花~
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探