莫队算法:
①适用问题:大量区间的个数统计问题,由于莫队是典型的离线算法,故不能解决区间存在修改的问题。
比较典型的问题有“给定一个大小为N的数组,数组中所有元素的大小<=N。你需要回答M个查询。每个查询的形式是L,R。你需要回答在范围[ L,R ]中至少重复3次的数字(或者至少重复一次的数,即这个区间中不同的数)的个数。 ”
②基本思想:其实莫队就是基于暴力大法而进行的优化,先来介绍暴力的方法:既然询问的是各个区间中的数的个数问题,那么可以开一个数组cnt,用来记录各个数字出现的次数,每次询问,就用cnt数组对[L,R]中的各个数进行个数统计。显而易见,当询问次数很多的时候,明显会很耗时。而仔细分析后,不难发现,多个区间查询时,做了很多“重复工作”,这也就浪费了大量时间,所谓重复工作是指,当前询问的区间中的一部分,也出现在之前的询问中,那么每次都对这些重复的部分进行重新统计,就浪费了很多时间。
基于这个,莫队就是很好的利用了之前统计的区间,避免了很多重复统计。大概来说,就是设置两个指针L和R,对于每一个询问区间,分别移动L和R指针,直到L==当前询问区间的左边界 && R==当前询问区间的右边界。在移动L和R的过程中,同时更新统计信息(也就是cnt数组以及当前询问区间的答案)。
具体的讲解可以参见:https://blog.youkuaiyun.com/hnshhslsh/article/details/50582926
③模板代码:见这篇博客:https://www.cnblogs.com/five20/p/7603849.html
注意++L,L--等:何时前++和何时后++
④例题:
大视野oj1878:https://www.lydsy.com/JudgeOnline/problem.php?id=1878
AC代码:
#include <map>
#include <set>
#include <stack>
#include <cmath>
#include <queue>
#include <vector>
#include <cstdio>
#include <string>
#include <cstring>
#include <sstream>
#include <iostream>
#include <algorithm>
#define Fin freopen("in.txt","r",stdin)
#define Fout freopen("out.txt","w",stdout)
#define Case(T) int T;for(scanf("%d",&T);T--;)
#define fo(i,a,b) for(int i = a; i < b; ++i)
#define fd(i,a,b) for(int i = a; i >= b; --i)
#define me(a,b) memset(a,b,sizeof(a))
#define fi(a,n,val) fill(a,a+n,val)
#define Scand(n) scanf("%d",&n)
#define Scand2(a,b) scanf("%d%d",&a,&b)
#define Scand3(a,b,c) scanf("%d%d%d",&a,&b,&c)
#define Scand4(a,b,c,d) scanf("%d%d%d%d",&a,&b,&c,&d)
#define Scans(s) scanf("%s",s)
using namespace std;
typedef long long ll;
ll gcd(ll a,ll b) { return b ? gcd(b,a%b): a; }
const int maxn = 1e6 + 50;
const int INF = 0xffffff;
#ifndef ONLINE_JUDGE
#endif // ONLINE_JUDGE
int n,m; //n是整个区间的数字个数,m是查询区间的个数
int arr[maxn]; //arr是整个区间
int id[maxn]; //各个数字所属的块的编号
int ans[maxn]; //各个区间询问的答案
int cnt[maxn]; //当前区间中各个数出现的次数
int now; //记录当前区间中不同数的个数
struct range{
int l,r; //区间的左右边界
int askOrder; //区间被询问的次序
// friend bool operator < (const range &a, const range &b){
// if(id[a.l] == id[b.l]) //同一个块中的,按右边界进行排序
// return a.r < b.r;
// return id[a.l] < id[b.l]; //非同一个块的,按所属的块进行排序
// }
bool operator <(const range & b)const{
if(id[l] == id[b.l])
return r < b.r;
return l < b.l;
}
};
range rangee[maxn];
//分块
void blocker(){
int k = sqrt(n);
fo(i, 1, n+1){
id[i] = (i-1)/k+1;
}
}
//x是指针,type表示指针是否在当前区间内,1表示在,0表示不在
void move(int x, int type){
if(type == 1){
cnt[arr[x]]++;
if(cnt[arr[x]] == 1) //arr[x]这个数的个数从0增加到1,说明该区间新增了一个数
now++;
}else if(type == 0){
cnt[arr[x]]--;
if(cnt[arr[x]] == 0) //arr[x]这个数的个数从1减少为0,说明该区间减少了一个数
now--;
}
}
inline int read(){
int sgn = 1; int cnt = 0; //sgn表示正负号 cnt表示读取的数字
char ch = getchar();
while (ch < '0' || ch > '9') { //读取非数字的字符,保留负号,忽略其余无关符号
if(ch == '-')
sgn = -sgn;
ch = getchar();
}
while ('0' <= ch && ch <= '9') {
cnt = cnt*10 + (ch-'0');
ch = getchar();
}
return sgn*cnt;
}
int main()
{
#ifndef ONLINE_JUDGE
//Fin;
#endif // ONLINE_JUDGE
n = read();
fo(i, 1, n+1)
arr[i] = read();
blocker();
m = read();
fo(i, 1, m+1){
rangee[i].l = read(); rangee[i].r = read();
rangee[i].askOrder = i;
}
sort(rangee+1, rangee+m+1);
int L = 0, R = 0; //左右指针
now = 0;
for(int i = 1; i <= m; ++i){
//指针不在当前区间内 注意是L++、R++
while (L < rangee[i].l) {
move(L++,0);
}
while (R > rangee[i].r) {
move(R--,0);
}
//指针在当前区间内 注意是--L、--R
while (L > rangee[i].l) {
move(--L,1);
}
while (R < rangee[i].r) {
move(++R,1);
}
ans[rangee[i].askOrder] = now; //按询问的次序记录答案
}
//输出答案
fo(i, 1, m+1){
printf("%d\n",ans[i]);
}
return 0;
}
本文介绍了莫队算法的基本原理和应用场景,适用于大量区间的个数统计问题。通过优化暴力解法,利用之前已统计的区间避免重复计算,提高效率。文章包含实现细节及代码示例。
8万+

被折叠的 条评论
为什么被折叠?



