莫队算法-离线+分块-两个模板题

莫队算法特点:离线,时间复杂度n1.5

  • 对数组进行分块,分块的大小是n0.5,数组被分成了n0.5块。
  • 将所有查询按第一关键字为左边界的分块索引,第二关键字为右边界进行排序。详见代码cmp函数。
  • 排序完成以后,离线回答每个询问。

在模板基础上,一般是修改add和del函数,指针移动时,区间维护内容稍有不同,详见下面的两个题目。

题目一:SPOJ DQUERY - D-query

Given a sequence of n numbers a1, a2, …, an and a number of d-queries. A d-query is a pair (i, j) (1 ≤ i ≤ j ≤ n). For each d-query (i, j), you have to return the number of distinct elements in the subsequence ai, ai+1, …, aj.

输入描述:

Line 1: n (1 ≤ n ≤ 30000).
Line 2: n numbers a1, a2, …, an (1 ≤ ai ≤ 106).
Line 3: q (1 ≤ q ≤ 200000), the number of d-queries.
In the next q lines, each line contains 2 numbers i, j representing a d-query (1 ≤ i ≤ j ≤ n).

输出描述:

For each d-query (i, j), print the number of distinct elements in the subsequence ai, ai+1, …, aj in a single line.

输入样例:

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

输出样例:

3
2
3

大概思路:

cnt[a[x]]表示x位置上的数值a[x]在两个指针之间出现的次数,当次数在0与1之间转换时,不同数字个数s改变。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=3e4+20,M=1e6+20,Q=2e5+20;
int n,m,cl=1,cr,bl,a[N],res[Q],s,cnt[M];
struct node{
	int l,r,id;
}t[Q];
int cmp(const node &s1,const node &s2){//莫队灵魂所在 
	return (s1.l/bl==s2.l/bl)?(s1.r<s2.r):(s1.l<s2.l);
}
inline void add(int x){//指针移动区间扩大 
	if(!cnt[a[x]]) s++;
	cnt[a[x]]++;
}
inline void del(int x){//指针移动区间缩小 
	cnt[a[x]]--;
	if(!cnt[a[x]]) s--;
}
signed main(){
	scanf("%d",&n);	bl=sqrt(n);
	for(int i=1;i<=n;i++)	scanf("%d",&a[i]);
	scanf("%d",&m);
	for(int i=1;i<=m;i++)	scanf("%d %d",&t[i].l,&t[i].r),t[i].id=i;
	sort(t+1,t+1+m,cmp);//查询离线 
	for(int i=1;i<=m;i++){//按块移动指针 
		int L=t[i].l;	int R=t[i].r; 
		while(cr<R)	add(++cr);
		while(cl>L)	add(--cl);
		while(cr>R)	del(cr--);
		while(cl<L)	del(cl++);
		res[t[i].id]=s;
	}
	for(int i=1;i<=m;i++)	printf("%d\n",res[i]);
	return 0;
}

题目二:CF Little Elephant and Array

The Little Elephant loves playing with arrays. He has array a, consisting of n positive integers, indexed from 1 to n. Let’s denote the number with index i as ai.

Additionally the Little Elephant has m queries to the array, each query is characterised by a pair of integers lj and rj (1 ≤ lj ≤ rj ≤ n). For each query lj, rj the Little Elephant has to count, how many numbers x exist, such that number x occurs exactly x times among numbers alj, alj + 1, …, arj.

Help the Little Elephant to count the answers to all queries.

输入描述:

The first line contains two space-separated integers n and m (1 ≤ n, m ≤ 105) — the size of array a and the number of queries to it. The next line contains n space-separated positive integers a1, a2, …, an (1 ≤ ai ≤ 109). Next m lines contain descriptions of queries, one per line. The j-th of these lines contains the description of the j-th query as two space-separated integers lj and rj (1 ≤ lj ≤ rj ≤ n).

输出描述:

In m lines print m integers — the answers to the queries. The j-th line should contain the answer to the j-th query.

输入样例:

7 2
3 1 2 2 3 3 7
1 7
3 4

输出样例:

3
1

大概思路:

cnt[a[x]]表示x位置上的数值a[x]在两个指针之间出现的次数,当次数在a[x]上下转换时,答案s改变。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,m,cl=1,cr,bl,a[N],res[N],s,cnt[N];
struct node{
	int l,r,id;
}t[N];
int cmp(const node &s1,const node &s2){
	return (s1.l/bl==s2.l/bl)?(s1.r<s2.r):(s1.l<s2.l);
}
inline void add(int x){
	++cnt[a[x]];	s+=(cnt[a[x]]==a[x]);	s-=(cnt[a[x]]==a[x]+1);
}
inline void del(int x){
	--cnt[a[x]];	s+=(cnt[a[x]]==a[x]);	s-=(cnt[a[x]]+1==a[x]);
}
signed main(){
	scanf("%d %d",&n,&m);	bl=sqrt(n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		if(a[i]>n)a[i]=N-1;
	}
	for(int i=1;i<=m;i++)	scanf("%d %d",&t[i].l,&t[i].r),t[i].id=i;
	sort(t+1,t+1+m,cmp);
	for(int i=1;i<=m;i++){
		int L=t[i].l;	int R=t[i].r; 
		while(cr<R)	add(++cr);
		while(cl>L)	add(--cl);
		while(cr>R)	del(cr--);
		while(cl<L)	del(cl++);
		res[t[i].id]=s;
	}
	for(int i=1;i<=m;i++)	printf("%d\n",res[i]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值