数据结构 分块 & 莫队

分块

一种优化暴力的思想。

通常是将原数据划分成适当块(一般为 \(\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;

区间查询

查询某区间 \(\leftL,R\\right\) 内信息,主要分为两种情况:

  1. 查询的区间位于一个块内,直接暴力即可,最坏复杂度为 \(sq\);
  2. 查询的区间跨越多个块,分为三部分求解:\(\leftL,ed_{bl_{L}}\\right\),中间完整块统计,和最后 \(\lefted_{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;
}

区间修改

情况同查询,分为两种:

  1. 在同一块内,直接暴力修改;
  2. 不在同一块内,对于开头结尾两不完整的块暴力修改,中间完整块用 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)}\) 的复杂度从已知区间 \(\leftl,r\\right\) 的答案得出 \(\leftl-1,r\\right\),\(\leftl+1,r\\right\),\(\leftl,r-1\\right\),\(\leftl,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;
}

末

不同数据结构都有各自优点,也都有共通之处,我们应该学到每个思想真正优势的地方,一些较麻烦的可以通过转换方法来解决(所以没写带修莫队)。

本文代码全部线上手打,有问题请指出,感谢支持。


完结撒花~

相关推荐
程序员沉梦听雨4 天前
【搜索引擎】Kibana使用指南
java·后端·学习笔记
死磕音视频5 天前
Gemini Notebook 入门:把公开 YouTube 教程变成可提问的学习笔记
学习笔记·youtube·gemini·notebooklm
宇宙无敌暴龙战士6666 天前
MySQL 8 库表操作学习笔记:SQL 基础、库表管理、约束与用户授权全流程
数据库·sql·mysql·学习笔记·后端开发
曲径海盗11 天前
【Python 学习第 41 天】for 循环
python·学习笔记·编程入门
曲径海盗12 天前
【Python 学习第 38 天】for 循环
python·学习笔记·编程入门
曲径海盗12 天前
【Python 学习第 40 天】for 循环
python·学习笔记·编程入门
曲径海盗13 天前
【Python 学习第 37 天】for 循环
python·学习笔记·编程入门
曲径海盗13 天前
【Python 学习第 39 天】for 循环
python·学习笔记·编程入门
曲径海盗20 天前
【Python 学习第 34 天】条件判断 if/else
python·学习笔记·编程入门
泡泡糖的中文规格书21 天前
HDLbits 1.2 学习笔记
fpga开发·学习笔记·verilog