ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

莫队(离线处理区间询问)

2021-10-13 20:34:38  阅读:215  来源: 互联网

标签:5000 优先级 询问 离线 include 移动 莫队


我第一次听说莫队这个算法是好久好久以前了,我一直认为这是一个比较难的算法,所以从未尝试去学习他,直到我今天看了莫队,我才知道莫队其实就是一个利用分块思想对暴力解决区间询问问题的一个优化。

下面我将一步步分析莫队的优化过程,我先来引入一个例子:

 这道问题我们应该怎么解决呢?一开始肯定是暴力的做法了,我们可以初始定义一个l,r,并计算出区间[ l , r ]的答案,不妨假设下一次询问是[ l - p , r - q ],那么我们就可以左移l,每次左移一次就加上l的贡献,左移r,每次左移就减去r的贡献。这样算到最后就能得到区间[ l - p , r - q ]的答案。但是这样会有一个问题:就是区间询问如果是两个询问之间距离差距较大的话,这样复杂度是很高的,比如(1,10),(10000,10008),(2,15),(10050,10100)……,这样的区间,l从1移动到10000再移动到2,再移动到10050,每次跨度都很大,这样复杂度巨高。

这时候肯定有同学会想到这样的解决方法,就是按照每个询问的l为第一优先级,r为第二优先级进行从小到大排序,然后再按照之前的暴力思路进行做。

这的确是一个很好的优化思路,但是还是有数据可以卡住这种做法,比如(1,15),(100,200),(2,50),(1,5000),(2,4000),(3,3000),如果我们按照每个询问的l为第一优先级,r为第二优先级进行从小到大排序,排完序后的询问是(1,15),(1,5000),(2,50),(2,4000),(3,2000),(100,5000).这样的话l的移动顺序是从1->2->3->100,l一共是99次,而r的移动顺序是15->5000->50->4000->2000->5000,这样r一共是移动4985+4950+3950+2000+3000=18885次,显然r移动的次数太多,而如果我们按照下面这样的询问顺序进行询问:

(1,15),(2,50),(3,2000),(2,4000),(1,5000),(100,5000),这样的话l的移动顺序是1->2->3->2->1->100,移动次数是103次,r的移动顺序是15->50->2000->4000->5000->5000,r的移动次数一共是4985次,这样下面这种方法移动次数显然小于上面的移动次数。那下面这种方法就是利用了莫队的思想进行的优化。

那莫队究竟是怎样的思想呢,就是先对询问的左边界进行分块,然后按照询问左边界所在的块作为第一优先级,询问的右边界作为第二优先级进行排序。再按照暴力的思想进行求解,那一般怎样分块呢?其实根据数学推导得到当块的大小为总数的开方时,这样算法的复杂度是最低的,复杂度是o(n^1.5),证明我就简单写一下吧:

假设我们将整个区间分成x块,那么每块有n/x个, l 平均每次最多移动n/x次, r 每次最多移动n次这样对于m次询问复杂度就是max(n*m/x,m*x),显然当两者相等时,该式取得最小值,也就是x=sqrt(n)

下面说一下可以利用莫队解题的题目所具有的性质:

能够利用 [l , r ]的答案o(1)算出 [ l -1 , r ], [ l +1 , r ], [ l , r -1 ], [ l , r +1 ]区间的答案。

下面是模板:(以洛谷P2709为例)

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=1e5+10;
ll pn,n,m,k,ans;
//pn是分块的大小 
struct node{
	ll id,l,r,pl;
}p[N];
bool cmp(node a,node b)
{
	//按照询问左边界所在的块为第一优先级,右边界的大小为第二优先级进行排序
	if(a.pl!=b.pl) return a.pl<b.pl;
	return a.r<b.r;
}
ll cnt[N],sum[N],a[N];
//不同题目只需改变add和sub函数即可 
void add(ll x) 
{
	ans-=cnt[x]*cnt[x];
	cnt[x]++;
	ans+=cnt[x]*cnt[x];
}
void sub(ll x)
{
	ans-=cnt[x]*cnt[x];
	cnt[x]--;
	ans+=cnt[x]*cnt[x];
}
int main()
{
	scanf("%lld%lld%lld",&n,&m,&k);
	pn=sqrt(n);
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	for(int i=1;i<=m;i++)
	{
		scanf("%lld%lld",&p[i].l,&p[i].r);
		p[i].id=i;
		p[i].pl=(p[i].l-1)/pn+1;//将询问的左边界分块 
	}
	sort(p+1,p+m+1,cmp);
	ll l=1,r=0;
	for(int i=1;i<=m;i++)
	{
		while(l<p[i].l)
		{
			sub(a[l]);
			l++;
		}
		while(l>p[i].l)
		{
			l--;
			add(a[l]);
		}
		while(r<p[i].r)
		{
			r++;
			add(a[r]);
		}
		while(r>p[i].r)
		{
			sub(a[r]);
			r--;
		}
		sum[p[i].id]=ans;
	}
	//输出答案 
	for(int i=1;i<=m;i++)
		printf("%lld\n",sum[i]);
	return 0;
}

标签:5000,优先级,询问,离线,include,移动,莫队
来源: https://blog.csdn.net/AC__dream/article/details/120750676

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有