莫队
莫队算法是由莫涛巨佬提出的离线查询优化算法。
思想
使用莫队算法的前提是我们在知道某区间l,r的结果后,可以在很短的时间内求出l+1,r和l,r+1和l-1,r和l,r-1的结果
但是很多出题人就会卡你这种暴力转移,让你从头跑到尾又从尾跑到头
所以这时候我们就需要通过莫队算法来排序这些询问,让我们可以在尽量短的时间内跑完
实现
如何排序这些离线询问?
首先简化题意:给定一个序列,多次询问某区间内有多少不同的数字。
一个简单的想法:因为我们知道了l,r区间中有几个不同的数字,我们每次移动时只需要看看新加入的数字有没有出现过就好了
这样可以做到\(O(1)\)的移动
但是,当相邻两个询问特别远,我们就会耗费大量的时间,于是我们就可以使用莫队排序。
分块
这个分块不是指数据结构而是一种把问题分类的方式
我们把整体分成几个小块以方便后面的排序
最常见的块的大小是\(/sqrt(n)\)
代码:
int bl=sqrt(n);//即block lenth,块长
for(register int i=1;i<=n;++i){
read(a[i]);
be[i]=i/bl-1;
}
核心排序
我们上面已经分好块了,那我们接下来怎么办呢?
首先,按照每个询问的左端点所属的块进行排序,然后按照右端点的大小进行排序就可以了
代码:
bool cmp(query a,query b){
return be[a.l]==be[b.l]?a.r<b.r:a.l<b.l;
}
排序的优化:奇偶排序
首先我们的右端点经过排序之后貌似还是会跳来跳去
所以我们选择在第奇数个块的时候右端点按从小到大排序,第偶数个块的时候按从大到小排序,可以节省一些转移时间
代码:
bool cmp(query a,query b){
return be[a.l]==be[b.l]?(be[b.l]&1)?a.r<b.r:a.r>b.r:a.l<b.l;
}
完整代码
#include<bits/stdc++.h>
using namespace std;
inline void read(int &x){
char ch=getchar(); x=0;
while(ch<'0') ch=getchar();
while(ch>='0' && ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
}
char buf[201];
inline void out(int x){
register int cnt=0;
while(x)buf[++cnt]=(x%10)+48,x/=10;
while(cnt) putchar(buf[cnt--]);
putchar('\n');
}
struct query{
int l,r;
int id;
}q[1000001];
int be[1000001];
int cnt[1000001];
int ans[1000001];
int a[1000001];
int sum;
bool cmp(query a,query b){
return be[a.l]==be[b.l]?(be[b.l]&1)?a.r<b.r:a.r>b.r:a.l<b.l;
}
int main(){
int n,m;
read(n);
int bl=sqrt(n);
for(register int i=1;i<=n;++i){
read(a[i]);
be[i]=i/bl-1;
}
read(m);
for(register int i=1;i<=m;++i){
read(q[i].l),read(q[i].r);
q[i].id=i;
}
sort(q+1,q+1+m,cmp);//千万不要忘记排序
int l=1,r=0;
for(register int i=1;i<=m;++i){
while(r<q[i].r){cnt[a[++r]]++;if(cnt[a[r]]==1)sum++;}//cnt用于记录有没有出现过
while(r>q[i].r){cnt[a[r]]--;if(cnt[a[r--]]==0)sum--;}
while(l<q[i].l){cnt[a[l]]--;if(cnt[a[l++]]==0)sum--;}
while(l>q[i].l){cnt[a[--l]]++;if(cnt[a[l]]==1)sum++;}
ans[q[i].id]=sum;
}
for(register int i=1;i<=m;++i){
out(ans[i]);
}
}