莫隊算法及其應用


在寫這篇博客之前,我最想做的一件事就是:ORZ莫隊%%%%%%%%。

說明:ceil(x)表示x向上取整,sqrt(x)表示對x開算數平方根。

一、莫隊算法簡介

  莫隊算法是一種暴力算法,真的很暴力,但速度很快,屬於速度快的暴力。它的基本思想就是分塊。關於分塊的介紹建議參考hzwer的博客,然后%%%%hzw。莫隊算法主要用於解決一類離線查詢的問題,和線段樹處理的問題是一樣的,但處理的是兩個不同的方面,當由[L,R]轉移到[L’,R’]的時間為O(|L'-L|+|R'-R|)時適宜使用莫隊算法。這個可以從題目中體會。因為采取的是分塊它的復雜度是O(nsqrt(n))。其實質是將詢問按照某種順序排好,這個也應該從題目中去體會,我們參考一道題目。

二、典型例題

  著名例題,小Z的襪子

  鏈接:http://www.lydsy.com/JudgeOnline/problem.php?id=2038

  題目是中文的,看得懂所以不復制粘貼了。題意也不難理解,稍有組合數學常識的人都可以看出。

三、解法

  因為題目中的組合是C(n,2),所以我們預處理出C2數組,存放2-n對2的組合數,作為特例,C(0,2)=C(1,2)=0;

  我們用桶tab存放[L,R]中每種顏色的數量,假設我們求出了[L,R],求[L+1,R](或[L-1,R][L,R+1][L,r-1])時只需要把桶里面的--或++就可以了,令[L,R]的答案為ans,那么[L+1,R]的答案為ans-C(tab[L],2)+C(tab[L]+1,2),這是O(1)的;

  我們可以發現,假設我們求出了[L,R],那么我們求出[L’,R’]的時間為O(|L'-L|+|R'-R|),所以我們采用莫隊算法。

  數據范圍是n,m<=50000,這啟發我們用分塊(當然如果執意要寫曼哈頓最小生成樹那也沒人攔你)。我們先將所有詢問按照l為第一關鍵字,r為第二關鍵字排一遍序,再將排好序的數組分成[√n]塊,再將分好塊的數組按照r大小排一遍序,這樣我們就做完了第一步了。

  接着我們按塊處理,對於每一塊,找出每個詢問和它前面一個詢問的差異,修改差異,不斷地這么做,就可以得到答案。

  這樣做總時間復雜度僅有O(n√n),比原有的O(n^2)的暴力快了許多,但這是為什么呢?

四、復雜度分析

  首先是分塊這一步,這一步的時間復雜度毫無疑問地是O(√n*√n*log√n+nlogn)=O(nlogn);

  接着就到了莫隊算法的精髓了,下面我們用通俗易懂的初中方法來證明它的時間復雜度是O(n√n);

  證:令每一塊中L的最大值為max1,max2,max3,...,maxceil(√n).

  由第一次排序可知,max1<=max2<=...<=maxceil(√n)

  顯然,對於每一塊暴力求出第一個詢問的時間復雜度為O(n)。

  考慮最壞的情況,在每一塊中,R的最大值均為n,每次修改操作均要將L由maxi-1修改至maxi或由maxi修改至maxi-1。

  考慮R:因為R在塊中已經排好序,所以在同一塊修改完它的時間復雜度為O(n)。對於所有塊就是O(n√n)。

  重點分析L:因為每一次改變的時間復雜度都是O(maxi-maxi-1)的,所以在同一塊中時間復雜度為O(√n*(maxi-maxi-1)).

    將每一塊L的時間復雜度合在一起,可以得到對於L的總時間復雜度為

    O(√n*(max1-1)+√n*(max2-max1)+√n*(max3-max2)+...+√n*(maxceil(√n)-maxceil(√n-1)))

      =O(√n*(max1-1+max2-max1+max3-max2+...+maxceil(√n-1)-maxceil(√n-2)+maxceil(√n)-maxceil(√n-1)))

      =O(√n*(maxceil(√n)-1))  (初中裂項求和)

  由題可知maxceil(√n)最大為n,所以L的總時間復雜度最壞情況下為O(n√n).

  綜上所述,莫隊算法的時間復雜度為O(n√n);

五、例題代碼

  還是用emacs寫的,所以還是兩格縮進,不喜勿噴。

  

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 typedef long long ll;
 4 ll a[60000],tab[60000];
 5 struct ask{
 6   ll l,r,num;
 7 }b[60000];
 8 ll cmp(ask x,ask y){
 9   if(x.l<y.l) return 1;
10   if(x.l>y.l) return 0;
11   if(x.r<y.r) return 1;
12   return 0;
13 }
14 ll comp(ask x,ask y){
15   if(x.r<y.r) return 1;
16   if(x.r>y.r) return 0;
17   if(x.l<y.l) return 1;
18   return 0;
19 }
20 ll gcd(ll a,ll b){
21   if(!b) return a;
22   return gcd(b,a%b);
23 }ll n,m;
24 ll comb2[60000];//組合數C(n,2)
25 ll prix[60000],priy[60000];//答案
26 ll rep(ll ol,ll nl,ll lr,ll nr,ll &ans){//回答修改的問題,原來的是[ol,lr],現在是[nl,nr];
27   if(ol<=nl)
28     for(ll i=ol;i<nl;i++){ans-=comb2[tab[a[i]]]; tab[a[i]]--;ans+=comb2[tab[a[i]]];} 
29   else
30     for(ll i=ol-1;i>=nl;i--){ans-=comb2[tab[a[i]]]; tab[a[i]]++;ans+=comb2[tab[a[i]]];} 
31   for(ll i=lr+1;i<=nr;i++){ans-=comb2[tab[a[i]]]; tab[a[i]]++;ans+=comb2[tab[a[i]]];} 
32   return ans;
33 }
34 
35 int main(){
36   scanf("%lld%lld",&n,&m);comb2[1]=comb2[0]=0;
37   for(ll i=2;i<=n;i++)comb2[i]=(ll)((double)i/2.0*(double)(i-1));//計算組合數 38   for(ll i=1;i<=n;i++) scanf("%lld",&a[i]);
39   for(ll i=1;i<=m;i++){
40     scanf("%lld%lld",&b[i].l,&b[i].r);
41     b[i].num=i;
42   }
43   ll sq=sqrt(m);
44   sort(b+1,b+m+1,cmp);//第一次排序 45   for(ll i=1;i<=m;i+=sq){
46     sort(b+i,b+min(i+sq,m+1),comp);//第二次排序 47   }
48   for(ll i=1;i<=m;i+=sq){
49     ll ed=min(m,i+sq-1);
50     memset(tab,0,sizeof(tab));ll maxx=0;
51     long long ans=0;ans=rep(b[i].l,b[i].l,b[i].l-1,b[i].r,ans);//同下 52     prix[b[i].num]=ans;priy[b[i].num]=comb2[b[i].r-b[i].l+1];//暴力算出每塊的第一個,其實這里可以不這么做,直接繼承上一塊也行 53     if(prix[b[i].num]==0)priy[b[i].num]=1;
54     else{ll g=gcd(prix[b[i].num],priy[b[i].num]);
55       prix[b[i].num]/=g;priy[b[i].num]/=g;}//約分 56     for(ll j=i+1;j<=ed;j++){
57       prix[b[j].num]=rep(b[j-1].l,b[j].l,b[j-1].r,b[j].r,ans);//從上一個詢問推導這一個詢問 58       priy[b[j].num]=comb2[b[j].r-b[j].l+1];
59       if(prix[b[j].num]==0)priy[b[j].num]=1;
60       else{
61       ll g=gcd(prix[b[j].num],priy[b[j].num]);
62       prix[b[j].num]/=g;priy[b[j].num]/=g;
63       }
64     }
65   }
66   for(ll i=1;i<=m;i++){
67     printf("%lld/%lld\n",prix[i],priy[i]);//這里需要注意,BZOJ有坑,cout是會RE的 68   }
69   return 0;
70 }

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM