轉載自https://www.cnblogs.com/Paul-Guderian/p/6933799.html
原文更爲清晰仔細,並同時解釋帶修莫隊與樹上莫隊。
自己學習。
目前的題型概括爲三種:普通莫隊,樹形莫隊以及帶修莫隊。
若談及入門,那麼BZOJ2038的美妙襪子一題堪稱頂尖。
【例題一】襪子
·述大意:
進行區間詢問[l,r],輸出該區間內隨機抽兩次抽到相同顏色襪子的概率。
·分析:
首先考慮對於一個長度爲n區間內的答案如何求解。題目要求Ans使用最簡分數表示:那麼分母就是n*n(表示兩兩襪子之間的隨機組合),分子是一個累加和,累加的內容是該區間內每種顏色i出現次數sum[i]的平方。
將莫隊算法擡上議程。莫隊算法的思路是,離線情況下對所有的詢問進行一個美妙的SORT(),然後兩個指針l,r(本題是兩個,其他的題可能會更多)不斷以看似暴力的方式在區間內跳來跳去,最終輸出答案。
掌握一個思想基礎:兩個詢問之間的狀態跳轉。如圖,當前完成的詢問的區間爲[a,b],下一個詢問的區間爲[p,q],現在保存[a,b]區間內的每個顏色出現次數的sum[]數組已經準備好,[a,b]區間詢問的答案Ans1已經準備好,怎樣用這些條件求出[p,q]區間詢問的Ans2?
考慮指針向左或向右移動一個單位,我們要付出多大的代價才能維護sum[]和Ans(即使得sum[],Ans保存的是當前[l,r]的正確信息)。我們美妙地對圖中l,r的向右移動一格進行分析:
如圖啦。l指針向右移動一個單位,所造成的後果就是:我們損失了一個綠色方塊。那麼怎樣維護?美妙地,sum[綠色]減去1。那Ans如何維護?先看分母,分母從n2變成(n-1)2,分子中的其他顏色對應的部分是不會變的,綠色卻從sum[綠色]2變成(sum[綠色]-1)2 ,爲了方便計算我們可以直接向給Ans減去以前該顏色的答案貢獻(即sum[綠色]2)再加上現在的答案貢獻(即(sum[綠色]-1)2 )。同理,觀賞下面的r指針移動,將是差不多的。
·如圖r指針的移動帶來的後果是,我們多了一個橙色方塊。所以操作和上文相似,只不過是sum[橙色]++。
·迴歸正題地,我們美妙的發現,知道一個區間的信息,要求出旁邊區間的信息(旁邊區間指的是當前區間的一個指針通過加一減一得到的區間),竟只需要O(1)的時間。
·就算是這樣,到這裏爲止的話莫隊算法依舊無法煥發其光彩,原因是:如果我們以讀入的順序來枚舉每個詢問,每個詢問到下一個詢問時都用上述方法維護信息,那麼在你腦海中會浮現出l,r跳來跳去的瘋狂景象,瘋狂之處在於最壞情況下時間複雜度爲:O(n2)————如果要這樣玩,那不如寫一個暴力程序。
·“莫隊算法巧妙地將詢問離線排序,使得其複雜度無比美妙……”在一般做題時我們時常遇到使用排序來優化枚舉時間消耗的例子。莫隊的優化基於分塊思想:對於兩個詢問,若在其l在同塊,那麼將其r作爲排序關鍵字,若l不在同塊,就將l作爲關鍵字排序(這就是雙關鍵字)。大米餅使用Be[i]數組表示i所屬的塊是誰。排序如:
·值得強調的是,我們是在對詢問進行操作。
·時間複雜度分析(分類討論思想):
首先,枚舉m個答案,就一個m了。設分塊大小爲unit。
分類討論:
①l的移動:若下一個詢問與當前詢問的l所在的塊不同,那麼只需要經過最多2*unit步可以使得l成功到達目標.複雜度爲:O(m*unit)
②r的移動:r只有在Be[l]相同時纔會有序(其餘時候還是瘋狂地亂跳,你知道,一提到亂跳,那麼每一次最壞就要跳n次!),Be[l]什麼時候相同?在同一塊裏面l就Be[]相同。對於每一個塊,排序執行了第二關鍵字:r。所以這裏面的r是單調遞增的,所以枚舉完一個塊,r最多移動n次。總共有n/unit個塊:複雜度爲:O(n*n/unit)
總結:O(n*unit+n*n/unit)(n,m同級,就統一使用n)
根據基本不等式得:當n爲sqrt(n)時,得到莫隊算法的真正複雜度:
O(n*sqrt(n))
·代碼上來了(莫隊喜歡while):
1 #include<stdio.h> 2 #include<algorithm> 3 #include<iostream> 4 #include<math.h> 5 #include<cstring> 6 #define go(i,a,b) for(int i=a;i<=b;i++) 7 #define mem(a,b) memset(a,b,sizeof(a)) 8 #define ll long long 9 using namespace std;const int N=50003; 10 struct Mo{int l,r,ID;ll A,B;}q[N];ll S(ll x){return x*x;} 11 ll GCD(ll a,ll b){while(b^=a^=b^=a%=b);return a;} 12 int n,m,col[N],unit,Be[N];ll sum[N],ans; 13 bool cmp(Mo a,Mo b){return Be[a.l]==Be[b.l]?a.r<b.r:a.l<b.l;} 14 bool CMP(Mo a,Mo b){return a.ID<b.ID;}; 15 void revise(int x,int add){ans-=S(sum[col[x]]),sum[col[x]]+=add,ans+=S(sum[col[x]]);} 16 int main() 17 { 18 scanf("%d%d",&n,&m);unit=sqrt(n); 19 go(i,1,n)scanf("%d",&col[i]),Be[i]=i/unit+1;; 20 go(i,1,m)scanf("%d%d",&q[i].l,&q[i].r),q[i].ID=i; 21 22 sort(q+1,q+m+1,cmp); 23 24 int l=1,r=0; 25 go(i,1,m) 26 { 27 while(l<q[i].l)revise(l,-1),l++; 28 while(l>q[i].l)revise(l-1,1),l--; 29 while(r<q[i].r)revise(r+1,1),r++; 30 while(r>q[i].r)revise(r,-1),r--; 31 32 if(q[i].l==q[i].r){q[i].A=0;q[i].B=1;continue;} 33 q[i].A=ans-(q[i].r-q[i].l+1); 34 q[i].B=1LL*(q[i].r-q[i].l+1)*(q[i].r-q[i].l); 35 ll gcd=GCD(q[i].A,q[i].B);q[i].A/=gcd;q[i].B/=gcd; 36 } 37 38 sort(q+1,q+m+1,CMP); 39 go(i,1,m)printf("%lld/%lld\n",q[i].A,q[i].B); 40 return 0; 41 }//Paul_Guderian