首页 > 其他分享 >莫队学习笔记

莫队学习笔记

时间:2024-04-12 21:24:16浏览次数:15  
标签:cnt int 询问 笔记 学习 端点 排序 莫队 指针

目录

普通莫队

初遇——从暴力谈起

我们通过一道例题来讨论普通莫队。

题目链接

观察数据范围,一个很直接的想法是:开一个数组 \(cnt\),\(cnt_i\) 表示在询问的区间内数字 \(i\) 出现的次数。对于每一个询问,记询问区间左端点为 \(lt\),询问区间右端点为 \(rt\),对于 \(i\in [lt,rt],cnt_{a_{i}}\leftarrow cnt_{a_{i}}+1\),最后扫描一遍 \(cnt\) 数组统计 \(cnt\) 数组中有几个位置不为 \(0\),统计的结果即为这次询问的答案。记给定的 \(a\) 数组中最大的数为 \(K\),这个算法的时间复杂度在最坏的情况(即每个询问的左端点都是 \(1\),右端点都是 \(N\))下为 \(O(NQK)\),无法通过本题。

接下来我们考虑如何优化这个算法。

首先,对于 \(i\in [lt,rt]\),如果 \(cnt_{a_{i}}=0\),则说明 \(a_{i}\) 在询问的区间中第一次出现,答案加一,这样省去了扫描 \(cnt\) 数组的过程,在最坏的情况下时间复杂度降为 \(O(NQ)\),但是效率还是不够高。

接下来我们看几个询问:

1 5
2 6
5 7
...

我们注意到,这几个询问的区间有重复部分,解决每个询问时都会重复扫描重复部分,很浪费时间。于是,我们可以使用两个指针 \(l\) 和 \(r\),对于一个询问,我们不再直接扫描对应的区间,而是将 \(l\) 指针移动到对应的左端点,将 \(r\) 指针移动到对应的右端点,并且仅在 \(l\) 指针或 \(r\) 指针移动时对 \(cnt\) 数组和答案进行修改。

于是我们可以写出如下代码:

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,a[30005],m,cnt[1000005],now,l=1,r=0,lt,rt;
void add(int x){
	if(!cnt[a[x]]) now++;
	cnt[a[x]]++;
}
void del(int x){
	cnt[a[x]]--;
	if(!cnt[a[x]]) now--;
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	cin>>m;
	for(int i=1;i<=m;i++){
		cin>>lt>>rt;
		while(l<lt) del(l++);
		while(l>lt) add(--l);
		while(r<rt) add(++r);
		while(r>rt) del(r--);
		cout<<now<<endl;
	}
	return 0;
}

困境——乱跑的指针

当一个询问的左端点和右端点与上一个询问的左端点与右端点差距过大时,就会出现 \(l\) 指针和 \(r\) 指针在数列上乱跑的情况,无法保证效率。

例如当 \(N=30000\) 时的这几个询问:

1 2
29999 30000
3 4
29997 29998
...

这时, \(l\) 指针和 \(r\) 指针先从序列开头跑到了序列末尾,又从序列末尾跑到了接近序列开头的位置,然后又跑到了接近序列末尾的位置。

那么如何提高指针移动的效率呢?

我们观察到,指针的低效移动是由询问左右端点的无序性引起的,所以我们可以把询问离线,并将数列分块。按照左端点所处的块的位置升序排序,如果左端的所处的块位置相同,则按照右端点升序排序。这样,就在保证了左端点比较有序也保证了右端点比较有序,从而提高了指针的移动效率。

代码如下:

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int n,a[30005],m,cnt[1000005],now,l=1,r=0,L[30005],R[30005],pos[30005],t,ans[200005];
struct node{
	int l,r,id;
}q[200005];
inline bool cmp(node x,node y){
	if(pos[x.l]==pos[y.l]) return x.r<y.r;
	return pos[x.l]<pos[y.l];
}
inline void add(int x){
	if(!cnt[a[x]]) now++;
	cnt[a[x]]++;
}
inline void del(int x){
	cnt[a[x]]--;
	if(!cnt[a[x]]) now--;
}
signed main(){
	std::ios::sync_with_stdio(false);
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	t=sqrt(n);
	for(int i=1;i<=t;i++){
		L[i]=(i-1)*t+1;
		R[i]=i*t;
	}
	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;
		}
	}
	cin>>m;
	for(int i=1;i<=m;i++){
		cin>>q[i].l>>q[i].r;
		q[i].id=i;
	}
	sort(q+1,q+1+m,cmp);
	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) add(++r);
		while(r>q[i].r) del(r--);
		ans[q[i].id]=now;//注意,排序只有询问的顺序可能会改变,所以应该将答案存在ans[q[i].id]而不是ans[i]
	}
	for(int i=1;i<=m;i++) cout<<ans[i]<<endl;
	return 0;
}

优化——顺路而为之

我们考虑一种排序方式:

对于左端点所处的块不同的询问,按左端点升序排序。对于左端点所处的块相同的询问,若这个块是的编号是奇数,则按右端点升序排序,否则按右端点降序排序。

按照这样的方式排序后,\(r\) 指针能在处理完奇数块的询问后,可以在返回的途中顺路处理偶数块的询问,减少 \(r\) 指针的移动次数,从而提高效率。

代码如下:

bool cmp(node x,node y){
	if(x.l/t!=y.l/t) return x.l<y.l;
	if((x.l/t)&1) return x.r<y.r;
	return x.r>y.r;
}

带修莫队

咕咕咕

参考资料

OI-Wiki

标签:cnt,int,询问,笔记,学习,端点,排序,莫队,指针
From: https://www.cnblogs.com/ZnHF/p/18131048

相关文章

  • 深度学习-nlp-NLP之sequence2sequence--73
    目录1.sequence2sequence任务特点2.编码器与解码器参考:https://zhuanlan.zhihu.com/p/38816145sequence2sequence模型发展到今天,根据不同任务有着不同的变体。了解了最基本的框架之后,再看别的模型就没有太大问题了。1.sequence2sequence任务特点输入输出时不定长的。比......
  • SQL SERVER 从入门到精通 第5版 第三篇 高级应用 第10章 存储过程 读书笔记
    第10章存储过程 >.存储过程概述存储过程(storedprocedure)是预编译SQL语句的集合,这些语句存储在一个名称下并作为一个单元来处理.存储过程取代了传统的逐条执行SQL语句的方式.一个存储过程中可以包含增删改查等一系列SQL语句,当这个存储过程被调用时,这些操作也......
  • 算法学习笔记(13):同余最短路
    同余最短路是一种通过同余把状态分类,再通过建图跑最短路解决问题的算法。可以高效率解决一些特定的问题。非常的奇妙。算法鉴于学不懂,所以直接搬\(oi-wiki\)的题吧。呜呜呜。P3403跳楼机有一栋高为\(h\)的楼,初始在一楼,每次可以向上移动\(x\),\(y\),\(z\)层,也可......
  • node笔记1:vue+node+mongodb+studio 3T创建登录模块
    1.创建node项目:expressnodenpmipackage.json修改如下代码,便于每次修改代码都可以刷新页面:"scripts":{"start":"node-dev./bin/www"}2.如果配合node设置反向代理;3.添加mongoose模块提供数据库信息:npmimongoose--save4.以登录功能模块为例,项目文件如下:model......
  • mysql-子查询的学习
    子查询由一个具体的需求,引入子查询谁的工资比Abel的高SELECT*fromemployeesWHEREsalary>(SELECTsalaryFROMemployeesWHERElast_name='Abel')--自连接SELECTe2.*......
  • 四月十一日软件测试学习
      黑盒测试用例设计方法:1、等价类划分:他的具体操作方法,就是把所有可能的输入数据,包括有效输入数据和无效输入数据,给他划分成若干个等价的子集,给他起个名字就叫做等价类,使得每个子集中的典型值在测试中的作用与这一子集中其他值的作用相同。因为咱们输入的数据分为......
  • Rust教程 – 学习天文图像的多尺度处理
    最近,人们投入了大量精力开发新颖的图像处理技术。其中许多技术都源自于傅里叶和小波变换等数字信号处理方法。这些技术不仅使得各种图像处理技术如降噪、锐化和动态范围扩展成为可能,而且还使得计算机视觉中使用的许多技术如边缘检测、目标检测等成为可能。多尺度分析是相对较新......
  • VUE2.0版本学习笔记
    VUE2.0版本学习笔记脚手架安装npminstall-g@vue/clivuecreatevue2-practice#选择2.0版本如果执行中遇到错误,yarn的错误certificatehasexpired则执行yarncachecleanyarnconfigsetstrict-sslfalsecdvue2-practicenpmrunserve#初学者建议安装vsco......
  • 一些学习资料
    人工智能:机器学习、对环境的感知、实现动作机器学习学习:2.机器学习三要素:数据、算法、模型机器学习研究的是从数据中通过选取合适的算法,自动的归纳逻辑或规则,并根据这个归纳的结果(模型)与新数据来进行预测。3.深度学习是在机器学习的基础上实现的,得益于机器性能的提升。神经......
  • python-字典的学习
    '''在Python中,字典字是一系列键—值对值。每个键都与一个值相关联,你可以使用键来访问与之相关联的值。与键相关联的值可以是数字、字符串、列表乃至字典。事实上,可将任何Python对象用作字典中的值。在Python中,字典用放在花括号{}中的一系列键—值对表示'''customer={'name......