莫队算法,

本文介绍了莫队算法,一种针对区间询问问题的离线算法,其核心是分块思想。通过将数据分割成多个区间并维护区间边界,文章详细讲解了如何处理查询区间和区间操作,以及在实际例题中的应用。
摘要由CSDN通过智能技术生成

个人主页:仍有未知等待探索-CSDN博客

专题分栏:算法_仍有未知等待探索的博客-CSDN博客

目录

一、介绍

二、莫队算法

三、例题


一、介绍

莫队算法就是一个离线的,通常针对于区间询问的一种算法。

二、莫队算法

莫队算法的核心就是分块思想。将一个数据分成若干段,这样便于以较小的区间移动次数来完成区间查询的操作。

一般的话,都是\sqrt{n}为分段点进行分块。然后对查询区间进行排序。

我们需要对区间进行维护,通过移动区间的左右端点使区间到某个特定的区间,完成查询。(一般也要我们去维护其他变量,来达到目的)

三、例题

#define _CRT_SECURE_NO_WARNINGS 1

#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;

const int maxn = 500005;
int n, q;// 数组元素个数, q个询问
int a[maxn];// 数组元素
int l = 1, r = 0;// 要维护的区间左右端点
struct Q
{
	int l, r, k, rk;// 查询区间的左右端点,分块编号,输入的顺序
	int ans; // 记录在该查询区间内的答案
}query[maxn];

bool cmp1(struct Q x, struct Q y)
{
	if (x.k == y.k) return x.r < y.r;
	return x.l < y.l;
}
bool cmp2(struct Q x, struct Q y)
{
	return x.rk < y.rk;
}
int main()
{
	// 读数据
	cin >> n >> q;

	for (int i = 1; i <= n; i ++ ) cin >> a[i];
	// 计算分块点
	int sz = sqrt(n);
	// 存查询区间及其顺序
	for (int i = 1; i <= q; i ++ )
	{
		cin >> query[i].l >> query[i].r;
        // 分块是按照查询区间左端点分的
        // 不是查询区间的顺序(没有意义) 
		query[i].k = query[i].l / sz;// 计算分块编号
		query[i].rk = i; // 计算查询顺序
	}
	
	// 对查询区间进行排序,如果在同一个块内按右端点小的排,如果是不同块内按左端点小的排序

	sort(query + 1, query + 1 + q, cmp1);

	int l = 1, r = 0;// 自己维护的区间
	int cnt[maxn] = {0};// 根据题意要维护的查询区间的元素出现的个数
	int res = 0;// 记录答案
	for (int i = 1; i <= q; i ++ )
	{
		// 加贡献
		// 如果当前区间的左端点大于要查询的区间左端点,需要对区间进行扩张操作
		while (l > query[i].l)
		{
			l -- ;
			// 在新更新的左端点还没加入记录的时候,如果==1的话,加入的话就是2,满足条件,加入答案
			if (cnt[a[l]] == 1) res ++ ;
			// 在新更新的左端点还没加入记录的时候,如果==2的话,加入的话就是3,不满足条件,但是经历过==2的时候,已经被加入过条件,所以需要进行对答案进行 - 1 
			if (cnt[a[l]] == 2) res -- ;
			// 更新cnt
			cnt[a[l]] ++ ;
		}
		// 如果当前区间的右端点小于要查询的区间右端点,需要对区间进行扩张操作
		while (r < query[i].r)
		{
			r ++ ;
			if (cnt[a[r]] == 1) res ++ ;
			if (cnt[a[r]] == 2) res -- ;
			cnt[a[r]] ++ ;
		}
		// 减贡献
		// 如果当前区间的左端点小于要查询的区间左端点,需要对区间进行缩小操作
		while (l < query[i].l)
		{
			// 如果在还没缩小区间的时候,cnt==2,缩小后cnt==1,不符合答案条件,则对答案进行 -1
			if (cnt[a[l]] == 2) res -- ;
			// 如果在还没缩小区间的时候,cnt==3,缩小后cnt==2,符合答案条件,则对答案进行 +1
			if (cnt[a[l]] == 3) res ++ ;
			cnt[a[l]] -- ;
			l ++ ;
		}
		// 如果当前区间的右端点大于要查询的区间右端点,需要对区间进行缩小操作
		while (r > query[i].r)
		{
			if (cnt[a[r]] == 2) res -- ;
			if (cnt[a[r]] == 3) res ++ ;
			cnt[a[r]] -- ;
			r -- ;
		}

		// 得出答案
		query[i].ans = res;
	}

	sort(query + 1, query + 1 + q, cmp2);

	for (int i = 1; i <= q; i ++ )
	{
		cout << query[i].ans << endl;
	}

	return 0;
}

  • 10
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

仍有未知等待探索

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值