莫隊算法(離線)

何謂莫隊算法

莫隊算法是莫濤隊長髮明的,爲表示對他的尊敬,故稱這種算法叫莫隊。算法


適用範圍

一種處理序列操做的離線算法,適用範圍廣,複雜度通常帶根號。數據結構


莫隊算法的思路

假設題目不涉及修改操做。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++

相關文章
相關標籤/搜索