莫隊算法

轉載自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?

image

考慮指針向左或向右移動一個單位,我們要付出多大的代價才能維護sum[]和Ans(即使得sum[],Ans保存的是當前[l,r]的正確信息)。我們美妙地對圖中l,r的向右移動一格進行分析:

                                    image

如圖啦。l指針向右移動一個單位,所造成的後果就是:我們損失了一個綠色方塊。那麼怎樣維護?美妙地,sum[綠色]減去1。那Ans如何維護?先看分母,分母從n2變成(n-1)2,分子中的其他顏色對應的部分是不會變的,綠色卻從sum[綠色]2變成(sum[綠色]-1)2 ,爲了方便計算我們可以直接向給Ans減去以前該顏色的答案貢獻(即sum[綠色]2)再加上現在的答案貢獻(即(sum[綠色]-1)2 )。同理,觀賞下面的r指針移動,將是差不多的。

                                      image

·如圖r指針的移動帶來的後果是,我們多了一個橙色方塊。所以操作和上文相似,只不過是sum[橙色]++。

·迴歸正題地,我們美妙的發現,知道一個區間的信息,要求出旁邊區間的信息(旁邊區間指的是當前區間的一個指針通過加一減一得到的區間),竟只需要O(1)的時間。

·就算是這樣,到這裏爲止的話莫隊算法依舊無法煥發其光彩,原因是:如果我們以讀入的順序來枚舉每個詢問,每個詢問到下一個詢問時都用上述方法維護信息,那麼在你腦海中會浮現出l,r跳來跳去的瘋狂景象,瘋狂之處在於最壞情況下時間複雜度爲:O(n2)————如果要這樣玩,那不如寫一個暴力程序。

·“莫隊算法巧妙地將詢問離線排序,使得其複雜度無比美妙……”在一般做題時我們時常遇到使用排序來優化枚舉時間消耗的例子。莫隊的優化基於分塊思想:對於兩個詢問,若在其l在同塊,那麼將其r作爲排序關鍵字,若l不在同塊,就將l作爲關鍵字排序(這就是雙關鍵字)。大米餅使用Be[i]數組表示i所屬的塊是誰。排序如:

image

·值得強調的是,我們是在對詢問進行操作。

·時間複雜度分析(分類討論思想):

首先,枚舉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

複製代碼

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