莫隊知識點講解

何謂莫隊

一種將詢問以一種特定的順序處理來支持離線處理區間詢問的算法。

實現方式

舉個栗子:

BZOJ1878 HH的項鍊

給一個長爲nn的序列,取出其中的一段區間[L,R][L,R],問其中有多少不同的數字。 n5×104n\le5\times10^4

如果有一段區間[L,R][L,R]中的數爲{1,1,2,3,1,2,4}\{1,1,2,3,1,2,4\},我們思考將其變爲[L,R1][L,R-1][L,R+1][L,R+1]答案的變化。

我們記錄一下每個數的出現次數,如果區間變爲[L,R1][L,R-1],那麼44的出現次數就由11變爲00,所以44就不在當前的區間中,種類數1-1

再思考區間變爲[L,R+1][L,R+1]的情況,如果加入的是33,那麼33的出現次數由11變爲22,對答案無任何影響,但如果加入的是55,那麼55的出現次數由00變爲11,答案+1+1

區間變爲[L1,R][L-1,R][L+1,R][L+1,R]的情況同理。

所以對於兩個詢問[2,3][2,3][1,5][1,5],我們只需移動兩次右區間,移動一次左區間,所以我們就得到了一個上界爲O(n2)O(n^2)的算法,但對於隨機數據遠遠跑不滿。

但這樣還遠遠不夠,在極端數據下,算法還是O(n2)O(n^2)的時間複雜度。所以我們可以想到將詢問以另外的一種順序進行處理,來使得時間複雜度更小。

一種想法是將詢問的左端點進行分塊,排序時第一關鍵字爲左端點的塊的編號,第二關鍵字爲右端點的大小,這樣做會使時間複雜度達到O(nn)O(n\sqrt n)

時間複雜度證明

下面的證明均假設n,mn,m同階。

左端點所在的塊是單調遞增的,所以每次詢問左端點最多在一個塊內移動,最壞從最右端移到最左端,所以最壞移動次數爲nnn\sqrt n

當左端點在同一塊內時,右端點單調遞增,最壞總移動步數爲O(nn)O(n\sqrt n),左端點移動到另一個塊時,右端點最壞從nn移動到11,時間複雜度O(nn)O(n\sqrt n)

綜上,總時間複雜度爲O(nn)O(n\sqrt n)

代碼(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;
	}
}

注意

當使用莫隊算法時要注意先擴再縮,例如從[1,5][1,5][6,10][6,10]時,如果先移動左端點,那麼就會區間就會變爲[6,5][6,5],會導致不必要的RERE

//正確寫法
	while(r<R)  Ins(++r);
	while(l>L)  Ins(--l);
	while(r>R)  Del(r--);
	while(l<L)  Del(l++);

例題

小Z的襪子

小B的詢問

Mato的文件管理

Gty的二逼妹子序列

[HNOI2016]序列

題解
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章