何謂莫隊
一種將詢問以一種特定的順序處理來支持離線處理區間詢問的算法。
實現方式
舉個栗子:
給一個長爲的序列,取出其中的一段區間,問其中有多少種不同的數字。
如果有一段區間中的數爲,我們思考將其變爲與答案的變化。
我們記錄一下每個數的出現次數,如果區間變爲,那麼的出現次數就由變爲,所以就不在當前的區間中,種類數。
再思考區間變爲的情況,如果加入的是,那麼的出現次數由變爲,對答案無任何影響,但如果加入的是,那麼的出現次數由變爲,答案。
區間變爲和的情況同理。
所以對於兩個詢問與,我們只需移動兩次右區間,移動一次左區間,所以我們就得到了一個上界爲的算法,但對於隨機數據遠遠跑不滿。
但這樣還遠遠不夠,在極端數據下,算法還是的時間複雜度。所以我們可以想到將詢問以另外的一種順序進行處理,來使得時間複雜度更小。
一種想法是將詢問的左端點進行分塊,排序時第一關鍵字爲左端點的塊的編號,第二關鍵字爲右端點的大小,這樣做會使時間複雜度達到。
時間複雜度證明
下面的證明均假設同階。
左端點所在的塊是單調遞增的,所以每次詢問左端點最多在一個塊內移動,最壞從最右端移到最左端,所以最壞移動次數爲。
當左端點在同一塊內時,右端點單調遞增,最壞總移動步數爲,左端點移動到另一個塊時,右端點最壞從移動到,時間複雜度。
綜上,總時間複雜度爲。
代碼(HH的項鍊)
#include<bits/stdc++.h>
using namespace std;
struct node{
int l,r,id,pos,ans;
}b[1000005];
int a[1000005],cnt[1000005],now=0,len,l=1,r=0;
int Read(){
int x=0,f=1;
char ch=getchar();
while(!isdigit(ch)){
if(ch=='-') f=-1;
ch=getchar();
}
while(isdigit(ch)){
x=(x<<3)+(x<<1)+ch-'0';
ch=getchar();
}
return x*f;
}
bool cmp1(node p,node q){
if(p.pos!=q.pos) return p.pos<q.pos;
return p.r<q.r;
}
bool cmp2(node p,node q){
return p.id<q.id;
}
void Del(int x){
cnt[a[x]]--;
if(!cnt[a[x]]) now--;
}
void Ins(int x){
if(!cnt[a[x]]) now++;
cnt[a[x]]++;
}
void query(int x){
int L=b[x].l,R=b[x].r;
while(r<R) Ins(++r);
while(l>L) Ins(--l);
while(r>R) Del(r--);
while(l<L) Del(l++);
b[x].ans=now;
}
int main(){
int n,m;
n=Read();
len=sqrt(n);
for(int i=1;i<=n;i++){
a[i]=Read();
}
m=Read();
for(int i=1;i<=m;i++){
b[i].l=Read();
b[i].r=Read();
b[i].id=i;
b[i].pos=b[i].l/len;
}
sort(b+1,b+m+1,cmp1);
for(int i=1;i<=m;i++){
query(i);
}
sort(b+1,b+m+1,cmp2);
for(int i=1;i<=m;i++){
cout<<b[i].ans<<endl;
}
}
注意
當使用莫隊算法時要注意先擴再縮,例如從到時,如果先移動左端點,那麼就會區間就會變爲,會導致不必要的。
//正確寫法
while(r<R) Ins(++r);
while(l>L) Ins(--l);
while(r>R) Del(r--);
while(l<L) Del(l++);