首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

数据结构 分块 & 莫队

编程知识
2024年08月08日 15:07

分块

一种优化暴力的思想。

通常是将原数据划分成适当块(一般为 \(\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]\) 内信息,主要分为两种情况:

  1. 查询的区间位于一个块内,直接暴力即可,最坏复杂度为 \(sq\)
  2. 查询的区间跨越多个块,分为三部分求解:\(\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;
}

区间修改

情况同查询,分为两种:

  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)}\) 的复杂度从已知区间 \(\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})}\) 的复杂度内求出所有询问的答案。

询问预处理

要想使得每一步操作均尽可能的有效,即不进行大幅度的冗余操作,我们需要将乱序的询问提前进行排序。

通常情况下,排序标准为以左边界所属块为第一关键字,右边界为第二关键字进行升序排序,其最优性证明见下。

在一些极特殊情况,我们还可以通过奇偶化排序等方式进一步优化复杂度,通常情况下不必须使用。

复杂度分析

image

摘自 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(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;
}

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

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


完结撒花~

From:https://www.cnblogs.com/Ratio-Yinyue1007/p/18349132
本文地址: http://shuzixingkong.net/article/917
0评论
提交 加载更多评论
其他文章 Kotlin 循环与函数详解:高效编程指南
Kotlin中的循环结构让你能轻松遍历数组或范围内的元素。使用`for`循环结合`in`操作符,可以简洁地访问数组中的每个项,如字符串数组或整数数组。对于范围,可以用`..`来定义一系列连续的值并进行迭代。此外,Kotlin支持通过`break`和`continue`控制循环流程。函数则允许封装可复
在 React 项目中 Editable Table 的实现
我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。 本文作者:佳岚 可编辑表格在数栈产品中是一种比较常见的表单数据交互方式,一般都支持动态的新增、删除、排序等基础功能。 交互分类 可编辑表格一般为两种交互形式: 实时保存
在 React 项目中 Editable Table 的实现 在 React 项目中 Editable Table 的实现 在 React 项目中 Editable Table 的实现
keycloak~关于社区登录的过程说明
keycloak将第三方登录(社区登录)进行了封装,大体主要会经历以下三个过程: 打开社区认证页面,输入账号密码或者扫码,完成社区上的认证 由社区进行302重定向,回到keycloak页面 keycloak与社区完成一次oauth2授权码认证,通过社区返回的code来获取token,再通过token
清晰易懂二分查找算法 你确定不看吗?
@目录前言简介一、二分查找算法的原理是什么?1. 确定搜索范围:2. 计算中间位置:3. 比较中间元素:4. 调整搜索范围:5. 重复迭代:二、二分查找算法的优缺点是什么?优点:缺点:三、java 实现二分查找案例总结 前言 请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i、 提示:以下是本
清晰易懂二分查找算法 你确定不看吗?
机器学习的数学基础--向量,矩阵
机器学习与传统编程的一个重要区别在于机器学习比传统编程涉及了更多的数学知识。不过,随着机器学习的飞速发展,各种框架应运而生,在数据分析等应用中使用机器学习时,使用现成的库和框架成为常态,似乎越来越不需要数学知识了。 其实,现成的库和框架只是帮助我们简化机器学习的开发任务,如果想要对模型训练结果进行调
机器学习的数学基础--向量,矩阵 机器学习的数学基础--向量,矩阵 机器学习的数学基础--向量,矩阵
"揭秘CentosChina爬虫项目:掌握Scrapy框架的必备技巧与数据库设计"
你是否想深入了解如何使用Scrapy框架进行高效爬虫开发?本文将揭秘CentosChina爬虫项目,从项目需求分析、数据库表设计,到Scrapy框架的实用技巧,全方位解析。无论你是初学者还是资深开发者,这篇文章都将为你提供宝贵的经验与指导,助你在爬虫开发领域更上一层楼。
"揭秘CentosChina爬虫项目:掌握Scrapy框架的必备技巧与数据库设计" "揭秘CentosChina爬虫项目:掌握Scrapy框架的必备技巧与数据库设计" "揭秘CentosChina爬虫项目:掌握Scrapy框架的必备技巧与数据库设计"
基于SiliconCloud快速体验GraphRag.Net
SiliconCloud介绍 SiliconCloud 基于优秀的开源基础模型,提供高性价比的 GenAI 服务。 不同于多数大模型云服务平台只提供自家大模型 API,SiliconCloud上架了包括 Qwen、DeepSeek、GLM、Yi、Mistral、LLaMA 3、SDXL、Instan
基于SiliconCloud快速体验GraphRag.Net 基于SiliconCloud快速体验GraphRag.Net 基于SiliconCloud快速体验GraphRag.Net
git的快速入门(含常用指令)
目录概念什么是gitgit与GitHub有什么区别提交、仓库、分支git的使用从GitHub上下载别人的代码直接将代码下载到本地克隆仓库获取代码将自己的代码上传到GitHub 本文拟将用通俗的语言描述git的使用方法,如有出入,请批评指正 概念 什么是git Git可以想象成一个超级高效的&quot
git的快速入门(含常用指令) git的快速入门(含常用指令) git的快速入门(含常用指令)