莫隊算法是莫濤隊長髮明的,爲表示對他的尊敬,故稱這種算法叫莫隊。算法
一種處理序列操做的離線算法,適用範圍廣,複雜度通常帶根號。數據結構
假設題目不涉及修改操做。spa
將全部操做離線,將全部操做進行二元組排序,第一維是左端點所在塊的編號,第二維是右端點。blog
排序後,按照順序處理詢問,維護一個雙端隊列,同時維護隊列內區間的答案,每次從$[L,R]$的答案,擴展到$[L-1,R]$ $[L,R-1]$ $[L+1,R]$ $[L,R+1]$的答案,最終獲得當此詢問區間的答案。排序
假設序列長度爲$n$,詢問數爲$m$,分塊塊長爲$d$,那麼左端點在同一塊內時,左端點移動每次不超過$d$,右端點總共移動不超過$n$,顯然是$\Theta (m\times d+\frac{n^2}{d})$,令$d=\sqrt{n}$,$n,m$同階,則時間複雜度爲$\Theta(n\sqrt{n})$,常數很大。隊列
莫隊算法的精髓在於,離線的狀況下,經過調整詢問與修改間的順序,由已知的答案擴展到未知的答案,以優秀的計算順序換取優秀的時間複雜度。
每一個詢問$[L,R]$能夠看做平面上的整點$(L,R)$,和另外一個詢問$(l,r)$之間的曼哈頓距離即爲兩個詢問間轉移的代價。
咱們要作的就是經過調整順序,最小化詢問間轉移的代價之和。
最優的計算順序即爲這些點的曼哈頓最小生成樹,然而求解這東西很是麻煩,因而咱們採起直接排序這一更爲暴力的方法,得到時間複雜度和代碼複雜度的平衡。class
當題目要求的操做較爲複雜,限制性強,用線段樹等數據結構不容易維護信息,或者維護信息複雜度較高,且題目對於時間的要求較爲寬鬆時,支持離線,能夠考慮使用莫隊。
時間複雜度通常帶根號,帶修改莫隊複雜度更高,且常數大,若是能夠直接線段樹或者分塊幹掉的題目,儘可能不要使用莫隊,有些題跑的真的很慢(即便你算出的時間複雜度很是優秀)。擴展
適用條件:方法
在序列上由$[L,R]$的答案伸縮左右端點時,僅爲$\Theta (1)$的複雜度。某些$\Theta (\log n)$轉移答案的強行用莫隊作,甚至不如$\Theta O(n^2)$暴力跑得快。im
1.++和--是在前仍是在後。
2.通常狀況下四種操做的順序無所謂,可是有的題是有影響的,比方說:BZOJ4358:permu。
普通莫隊:
struct rec
{
int id;
int l;
int r;
int pos;
}q[N];
int a[N];
int ans[N];
bool cmp(rec a,rec b){return a.pos==b.pos?a.r<b.r:a.pos<b.pos;}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
int t=sqrt(n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
q[i].pos=(q[i].l-1)/t+1;
}
sort(q+1,q+m+1,cmp);
int l=1,r=0;
for(int i=1;i<=m;i++)
{
while(l>q[i].l)upd(--l,1);
while(r<q[i].r)upd(++r,1);
while(l<q[i].l)upd(l++,0);
while(r>q[i].r)upd(r--,0);
}
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
return 0;
}
奇偶莫隊:
bool cmp(rec a,rec b){return (a.pos)^(b.pos)?a.l<b.l:(((a.pos)&1)?a.r<b.r:a.r>b.r);}
rp++