朴素莫队算法

本文介绍了一种用于解决区间查询问题的有效算法——莫队算法。该算法适用于能在常数时间内维护区间变化的情况,并通过将区间分块及排序策略优化查询效率。文章详细解释了算法原理、时间复杂度分析及奇偶优化技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

莫队算法

莫队算法有很多,本篇只讲最普通的莫队算法,以及奇偶优化

  • 前提:

    • 莫队算法适用于在常数时间复杂度内进行维护一个区间的延伸或缩小一个单位;即l++或l–或r++或r–在常数时间复杂度内完成。主要用于区间上的查询。
    • 莫队需要离线处理询问。加一个时间维可以支持修改,这次先不写。
  • 思想:

    • 将区间分块,假设区间长度为N,我们以N1/2为块长,一共分了N1/2块。
    • 首先我们只关注每个询问的左端点,按每个左端点所在的块把他们排序。
    • 对于每一个块内,我们将询问按照右端点的大小排序。
    • 于是我们按照这个顺序来进行询问,只改变左右指针的位置,即可得到答案。
    • 例如N=9的时候,我们有
      • {5,6},{7,9},{2,3},{1,2}四个询问;
      • 第一次关键字排完序{2,3},{1,2},{5,6},{7,9}
      • 第二关键字排完序{1,2},{2,3},{5,6},{7,9}
  • 具体操作:

    • 1.将所有的询问一次性读入并存储。
    • 2.将所有的区间按每个询问所在的分块为第一关键字,如果在同一分块内,以右端点的长度为第二关键字进行排序。
    • 3.对于每个排序通过移动左右指针来维护区间,得到查询的结果。

    时间复杂度O(n*n1/2)

证明

  • 首先对于我们只关注每次查询的右端点,对于每个分块,因为块内的R是有序的,r指针最差情况下从左边移动到最右边,就是O(n)的时间复杂度,有n1/2 个块,所以总时间复杂度是n3/2
  • 然后我们讨论左指针。左指针最差情况下是不断从块首移动到块尾,再移动到块首…如此反复,块内最长移动n1/2次,最多有n1/2这样的移动,所以对于每个块是O(n)的时间复杂度,对于所以块是n1/2*n的时间复杂度(总共有n1/2块)
  • 上述两者相加是O(n3/2),证毕

玄学优化

  • 对于块的大小的选择,在多次测试下,块的大小以n/√(m*2/3)时候最优
  • 奇偶优化
    • 思想:对于偶数块,我们的R按照从小到大排序,对于奇数块,R按照从大到小排序
    • 证明:对于朴素排序后的R,每一个分块结束后,R指针指向靠后的位置,到下一个分块后,R指针需要先回到左边然后再向右移动,如果此时我们直接从右到左获取查询,省去了回去的过程,能省下不少时间。

题目

小Z的袜子-洛谷

HH的项链-洛谷(60分即可)-数据加强后用莫队冲不过去了,但是加上奇偶优化以及吸吸氧(指开O2)能冲60%的数据,把这道题加上是因为是老传统了

代码

十分丑,勿骂

#include<bits/stdc++.h>
using namespace std;
#define fi(i, a, b) for(int i = (a); i <= (b); i++)
#define fd(i, a,b) for(int i = (a); i >= (b); i--)
#define sci(n) scanf("%d", &n)
#define scd(n) scanf("%lf", &n)
#define pfi(n) printf("%d", n)
#define pfl(n) printf("%lld", n)
#define pfn(n) putchar('\n')
#define sync ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
#define ii pair<int,int>
//#pragma GCC optimize(2)
int read(){int a=0,flag=1;char c=' ';while(c<'0'||c>'9'){c=getchar();if(c=='-')flag=0;}while(c<='9'&&c>='0'){a=(a<<3)+(a<<1)+c-'0';c=getchar();}if(flag)return a;return -a;}

const int N = 5e5 + 5;
int v[N];
int n,m; 
int unit;
int a[N];
int sum;
pair<int,int> ans[N];
void add(int x){
	sum+=v[x];
	v[x]++;
}
void del(int x){
	v[x]--;
	sum-=v[x];
}
struct P{
	int l,r;
	int id;
}q[N];
bool cmp(const P&tmp1,const P& tmp2){
	return ( (tmp1.l/unit)==(tmp2.l/unit ))? ( (int)(tmp1.l/unit)&1 ?tmp1.r<tmp2.r:tmp1.r>tmp2.r):(tmp1.l<tmp2.l);
}
int gcd(int a,int b){return b==0?a:gcd(b,a%b);}
int main(){
	#ifdef Potato
	freopen("in.txt", "r", stdin);
	#endif
	sync;
	cin>>n>>m;
	unit=(n/sqrt( (2.0/3.0*m) )+0.5);
//	cout<<unit<<'\n';
	for(int i=1;i<=n;i++){
		cin>>a[i];	}
	for(int i=0;i<n;i++){
		cin>>q[i].l>>q[i].r;
		q[i].id=i;
	}
	sort(q,q+m,cmp);
	for(int i=0,l=1,r=0;i<m;i++){
		int& id=q[i].id;
		if(q[i].l==q[i].r){
			ans[id]={0,1};
			continue;
		}
		while(l>q[i].l) add(a[--l]);
		while(r<q[i].r) add(a[++r]);
		while(l<q[i].l) del(a[l++]);
		while(r>q[i].r) del(a[r--]);
		long long tmp=r-l+1;
		tmp=tmp*(tmp-1)/2;
		ans[id]={sum,tmp};
		
	}
	for(int i=0;i<m;i++){
		int g=gcd(ans[i].first,ans[i].second);
		cout<<ans[i].first/g<<'/'<<ans[i].second/g<<'\n';
	}

	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值