莫隊演算法是用來處理一類無修改的離線區間詢問問題。——(摘自前國家隊隊長莫濤在知乎上對莫隊演算法的解釋。)
莫隊演算法是前國家隊隊長莫濤在比賽的時候想出來的演算法。
傳說中能解決一切區間處理問題的莫隊演算法。
準確的說,是離線區間問題。但是現在的莫隊被拓展到了有樹上莫隊,帶修莫隊(即帶修改的莫隊)。這裡只先講普通的莫隊。
還有一點,重要的事情說三遍!莫隊不是提莫隊長!莫隊不是提莫隊長!!莫隊不是提莫隊長!!!
看乙個例題:給定乙個n(n<50000)元素序列,有m(m<200000)個離線查詢。每次查詢乙個區間l~r,問每個元素出現次數為k的有幾個。(必須恰好是k,不能大於也不能小於)
這時候dalao們就直接樹狀陣列線段樹主席樹拍上去了,身為蒟蒻的我躲在角落瑟瑟發抖。該怎麼辦?
這時候就要使用莫隊演算法了。
接著上面的例題,直接暴力怎麼樣??
肯定會t的啊。
但是如果這個暴力我們給優化一下呢?
我們想,有兩個指標 curl 和 curr,curl 指向 l ,curr 指向 r。
l和r是乙個區間的左右兩端點。
利用 cnt 記錄每個數出現的次數,每次只是 cnt[a[curl]] cnt[a[curr]] 修改。
舉個栗子:
我們現在處理了curl—curr區間內的資料,現在左右移動,比如curl到curl-1,只需要更新上乙個新的3,即curl-1。
那麼curl到curl+1,我們只需要去除掉當前curl的值。因為curl+1是已經維護好了的。
curr同理,但是要注意方向哦!curr到curr+1是更新,curr到cur-1是去除。
我們先計算乙個區間[curl curr]的answer,這樣的話,我們就可以用o(1)轉移到[curl-1 curr] [curl+1 curr] [curl curr+1] [curl curr-1]上來並且求出這些區間的answer。
我們利用curl和curr,就可以移動到我們所需要求的[l r]上啦~
這樣做好像不會快很多,而且......
如果有個**資料,讓你在每個l和r間來回跑,而且跨度很大呢??
該\(t\)還是\(t\)...
怎麼辦?
但是這其實就是莫隊演算法的核心了。我們的莫隊演算法還有優化。
這就是莫隊演算法最精明的地方(我認為的qwq),也正是有了這個優化,莫隊演算法被稱為:優雅的暴力。
我們想,因為每次查詢是離線的,所以我們先給每次的查詢排乙個序。
一種直觀的辦法是按照左端點排序,再按照右端點排序。但是這樣的表現不好。特別是面對精心設計的資料,這樣方法表現得很差。
舉個栗子,有6個詢問如下:(1, 100), (2, 2), (3, 99), (4, 4), (5, 102), (6, 7)。
這個資料已經按照左端點排序了。用上述方法處理時,左端點會移動6次,右端點會移動移動98+97+95+98+95=483次。
其實我們稍微改變一下詢問處理的順序就能做得更好:(2, 2), (4, 4), (6, 7), (5, 102), (3, 99), (1, 100)。
左端點移動次數為2+2+1+2+2=9次,比原來稍多。右端點移動次數為2+3+95+3+1=104,右端點的移動次數大大降低了。
上面的過程啟發我們:
我們不應該嚴格按照公升序排序,而是根據需要靈活一點的排序方法
那麼排序的方法是
我們把所有的元素分成多個塊。再按照左端點塊編號從小到大排序,左端點塊編號相同按右端點塊編號從小到大。
這樣對於不同的查詢
例如:我們有長度為9的序列。
1 2 3 4 5 6 7 8 9 分為1——3 4——6 7——9
查詢有7組。[1 2] [2 9] [1 3] [6 9] [5 8] [3 8] [8 9]
排序後就是:[1 2] [1 3] [3 8] [2 9] | [5 8] [6 9] | [8 9]
然後我們按照這個順序移動指標就好啦~
時間複雜度證明
其實從不同的角度看,證法很多:對於左端點在乙個塊中時,右端點最壞情況是從盡量左到盡量右,所以右端點跳時間複雜度o(n),左端點一共可以在n0.5個塊中,所以總時間複雜度o(n*n0.5) = o(n^1.5)。
1.對於每組查詢的記錄和排序:
l,r為左右區間編號,p是第幾組查詢的編號
struct querye[maxn];
bool cmp(query a, query b)
2.處理和初始變數:
answer就是所求答案,bl是分塊數量,a是原序列,ans是記錄原查詢序列下的答案,cnt是記錄對於每個數i,cnt[i]表示i出現過的次數,curl和curr不再解釋,nmk題意要求。
int answer, a[maxn], m, n, bl, ans[maxn], cnt[maxn], k, curl = 1, curr = 0;
void add(int pos)//新增
void remove(int pos)//去除
//一般寫法都是邊處理 邊根據處理求答案。cnt[a[pos]]就是在pos位置上原序列a出現的次數。
3.主體部分及輸出:
預處理查詢編號,用四個while移動指標順便處理。
n = read(); m = read(); k = read();
bl = sqrt(n);
for(int i = 1; i <= n; i++)
a[i] = read();
for(int i = 1; i <= m; i++)
sort(e+1,e+1+m,cmp);
for(int i = 1; i <= m; i++)
for(int i = 1; i <= m; i++)
printf("%d\n",ans[i]);
return 0;
在這裡著重說下四個while
當curl < l 時,我們當前curl是已經處理好的了。所以remove時先去除當前curl再++
當curl > l 時,我們當前curl是已經處理好的了。所以 add 時先--再加上改後curl
當curr > r 時,我們當前curr是已經處理好的了。所以remove時先去除當前curr再--
當curr < r 時,我們當前curr是已經處理好的了。所以 add 時先++再加上改後curr
【luogu p2709 小b的詢問】
\(add\)和\(remove\)對於平方相加減的運算利用完全平方式逆回去。
1^2 = 1;
2^2 = (1+1)^2 = 1 + 1*2 + 1;
3^2 = (1+2)^2 = 1 + 2*2 + 4;
4^2 = (1+3)^2 = 1 + 3*2 + 9;
//小b的詢問
#include #include #include #include using namespace std;
const int maxn = 50001;
int answer, a[maxn], m, n, bl, ans[maxn], cnt[maxn], k, curl = 1, curr = 0;
void add(int pos)
void remove(int pos)
inline int read()
return k;
}struct querye[maxn];
bool cmp(query a, query b)
int main()
sort(e+1,e+1+m,cmp);
for(int i = 1; i <= m; i++)
for(int i = 1; i <= m; i++)
printf("%d\n",ans[i]);
return 0;
}
【luogu p4462 [cqoi2018]異或序列】
ax+ax-1+...+ay = cntx+cnty 這樣把一段序列變成兩段相加跑莫隊。
#include #include #include #include using namespace std;
const int maxn = 200010;
int curr = 0, curl = 1, answer,a[maxn], ans[maxn], cnt[maxn], n, m, k, bl;
struct queryq[maxn];
bool cmp(const query &a, const query &b)
for(int i = 1; i <= m; i++)
printf("%d\n",ans[i]);
return 0;
}
莫隊演算法適用條件是比較苛刻的嗎?是的。
①題目必須離線
②能夠以極少的時間推出旁邊區間(一般是o(1))
③沒有修改或者修改不太苛刻
④基於分塊,分塊不行,它也好不了**去
但莫隊的思想美妙,**優美,你值得擁有。莫隊的排序思想也為眾多離線處理的題目提供了完整的思路。
我們可以看出來,對於莫隊這種模擬式的暴力演算法很好理解。
也比較實用,亂搞神器不是浪得虛名。
莫隊是我自學的,所以在文章裡或許會有些個人理解上的偏差,還請各位dalao能賜教。自學能力是需要培養、鍛鍊的。
qvqqaq
qnq祝各位oi路途能越走越順!
本蒟蒻\(qq\) 935145183/3203600070
蒟蒻位址
莫隊演算法講解
問題 有n個數組成乙個序列,有m個形如詢問l,r的詢問,每次詢問需要回答區間內至少出現2次的數有哪些。樸素的解法需要讀取o nm 次數。如果資料範圍小,可以用陣列,時間複雜度為o nm 如果使用stl的map來儲存出現的次數,則需要o nmlogn 的複雜度。有沒有更快的方法呢?得出。如果能安排適當...
莫隊講解 普通莫隊
結束了分塊,我們來講下莫隊。據我所知,莫隊能解決一切區間問題,除了翻轉。因為它就是個暴力 其實這兩者的關係並不大。僅僅是時間複雜度一樣而已。我們把原序列分成 n塊 好像就是這裡相同 這裡說的序列是查詢序列l r,並不是讀入的a i 之後我們把序列排序 按照第一關鍵字為左端點所在的塊的大小,如果相同就...
HDU4638 莫隊演算法,講解)
解題思路 第一次做莫隊演算法,推薦部落格入門。結合上面那片部落格,我先講一下自己對莫隊演算法時間複雜度的理解。首先我們將乙個block設為sqrt n n為數列的長度。然後按提的問題的區間進行排序,排序的規則是 這麼做的目的是使l和r指標的移動複雜度盡可能相等。1.首先對l的移動複雜度進行分析。l最...