基础莫队算法

        从某种意义上说,莫队算法 属于那种理解起来比较抽象但是只要理解了就非常简单的一类算法,本质还是离不开暴力,适用于离线算法,如果要求是在线,则无法使用。

        接下来贴一道经典的模板题https://www.luogu.com.cn/problem/P2709

题目描述

        小B 有一个长为 𝑛 的整数序列 𝑎,值域为 [1,𝑘]。他一共有 𝑚 个询问,每个询问给定一个区间 [𝑙,𝑟],求:

                                ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​        \sum_{i = 1}^{k} ci^{2}

其中 𝑐i​ 表示数字 𝑖 在 [𝑙,𝑟][l,r] 中的出现次数。
小B请你帮助他回答询问。

输入格式

        第一行三个整数 𝑛,𝑚,𝑘。

        第二行 𝑛 个整数,表示 小B 的序列。

        接下来的 𝑚 行,每行两个整数 𝑙,𝑟。

输出格式

        输出 𝑚 行,每行一个整数,对应一个询问的答案。

输入输出样例

输入

6 4 3
1 3 2 1 1 3
1 4
2 6
3 5
5 6

输出 

6
9
5
2

说明/提示

【数据范围】
        对于 100%100% 的数据,1≤𝑛,𝑚,𝑘≤5×104。

        从这道题可以看出莫队算法对数组的区间操作非常的有优势,从暴力的角度,如果对于每一次查询都遍历一遍数组,那么时间复杂度会达到O(n2)。但是在查询的过程中,可以通过两个指针的左移右移操作,实现对区间的优化。

        第一遍优化:

        用指针的位移代替每一次重新遍历,优化大量时间。利用Add和Sub函数进行对新老目标的更新,计算其贡献。

        第二遍优化:

        但如果出现查询区间十分阴间的情况,例如[1,3][10,12][2,3],那么我们可以看见仅仅通过位移的方式依然没有任何优势,所以莫队算法使用了分块算法和双关键词排序的方法进一步优化,其中分块算法是把n长度的数组分成根号n的部分,并用pos记录下各个元素在哪一块部分

int siz = sqrt(n);
	
	for(int i = 1; i <= n; i ++)
	{
		cin >> a[i];
		pos[i] = i / siz;
	}//分块算法
struct node{
	int l;
	int r;
	int id;
}q[N];

bool cmp(const node x, const node y)
{
	if(pos[x.l] == pos[y.l])
			return x.r < y.r;
		return pos[x.l] < pos[y.l];
}//双关键词排序

        通过这两步骤,可以智慧地把指针相近的查询先行操作,节省了指针跳来跳去的时间浪费,把时间复杂度减少到了O(n*n1/2).、

        由此,我们可以得出莫队算法的基础模板,其中主函数是基本不变的模式,往往只需通过题意进行对Add和Sub函数的改变实现不同的问题解决。

        一下是主函数部分:

int main() 
{
	cin >> n >> m >> k;
	
	int siz = sqrt(n);
	
	for(int i = 1; i <= n; i ++)
	{
		cin >> a[i];
		pos[i] = i / siz;
	}
	
	for(int i = 0; i < m; i ++)
	{
		cin >> q[i].l >> q[i].r;
		q[i].id = i;
	}
		
	sort(q, q + m, cmp);
	
	int l = 1, r = 0;
	
	for(int i = 0; i < m; i ++)
	{
		while(q[i].l > l) Sub(l ++);
		while(q[i].l < l) Add(-- l);
		while(q[i].r > r) Add(++ r);
		while(q[i].r < r) Sub(r --);
		
		ans[q[i].id] = res;
	}
	
	for(int i = 0; i < m; i ++)
		cout << ans[i] << endl;
}

        除了Add和Sub函数,主函数中较难理解的是四段while语句,分别对应了当前指针与目标指针的对应位置而进行的操作,只有理解了这一部分就会觉得莫队算法非常简单。

        回到例题,针对问题要求,我们可以用cnt数组存储各个数出现的次数,并进行对答案res的更新。下面是具体代码:

void Add(int n)
{
	cnt[a[n]] ++;
	res += (cnt[a[n]]) * (cnt[a[n]]) - (cnt[a[n]] - 1) * (cnt[a[n]] - 1);
}

void Sub(int n)
{
	cnt[a[n]] --;
	res -= (cnt[a[n]] + 1) * (cnt[a[n]] + 1) - (cnt[a[n]]) * (cnt[a[n]]);
}

附上完整代码:

#include <bits/stdc++.h>

using namespace std;

const int N = 1e6 + 10;

struct node{
	int l;
	int r;
	int id;
}q[N];

int n, m, k, res = 0;
int a[N], pos[N], ans[N], cnt[N];

bool cmp(const node x, const node y)
{
	if(pos[x.l] == pos[y.l])
			return x.r < y.r;
		return pos[x.l] < pos[y.l];
}
void Add(int n)
{
	cnt[a[n]] ++;
	res += (cnt[a[n]]) * (cnt[a[n]]) - (cnt[a[n]] - 1) * (cnt[a[n]] - 1);
}

void Sub(int n)
{
	cnt[a[n]] --;
	res -= (cnt[a[n]] + 1) * (cnt[a[n]] + 1) - (cnt[a[n]]) * (cnt[a[n]]);
}

int main() 
{
	cin >> n >> m >> k;
	
	int siz = sqrt(n);
	
	for(int i = 1; i <= n; i ++)
	{
		cin >> a[i];
		pos[i] = i / siz;
	}
	
	for(int i = 0; i < m; i ++)
	{
		cin >> q[i].l >> q[i].r;
		q[i].id = i;
	}
		
	sort(q, q + m, cmp);
	
	int l = 1, r = 0;
	
	for(int i = 0; i < m; i ++)
	{
		while(q[i].l > l) Sub(l ++);
		while(q[i].l < l) Add(-- l);
		while(q[i].r > r) Add(++ r);
		while(q[i].r < r) Sub(r --);
		
		ans[q[i].id] = res;
	}
	
	for(int i = 0; i < m; i ++)
		cout << ans[i] << endl;
}

  • 15
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值