回滾莫隊
今天學習了回滾莫隊,忍不住手癢寫一下學習筆記。(bushi
回滾莫隊其實十分的簡單易懂。
為什麼需要回滾莫隊呢?
普通的莫隊可以O(1)維護從(L , R)轉移到(L ± 1 , R) , (L , R ± 1),但是,有一些維護值 擴充套件容易收縮難,或者 收縮容易擴充套件難。
比如,最大值的維護就是擴充套件容易收縮難。舉個例子:我們要從(L , R)轉移到(L+1 , R),那麼aL就不在區間中了,可是我們很難判斷aL是否為(L , R)中的最大值,如果是,且唯一,則(L+1 , R)的最大值就不再是aL,因為aL已經被移出區間,我們也不知道(L+1 , R)的最大值究竟是多少。這樣,就無法轉移了。
此時,回滾莫隊閃亮登場!
回滾莫隊的思想
只在左端點在同一分塊內時才轉移。當左端點在新的分塊內時,初始化所有。
因為左端點在同一分塊內的是按右端點排序,所以右端點肯定是單向擴充套件或收縮的,像普通莫隊一樣擴充套件即可。
左端點是亂序的,所以每次都從當前分塊的右邊界開始往左邊擴充套件到左端點,這樣就只會擴充套件而不會面臨收縮的局面(如果是收縮容易擴充套件難就從左邊界開始收縮)。
時間複雜度依然是O(n√n)。
回滾莫隊的具體實現(擴充套件容易收縮難為例)
1. 對原序列進行分塊,並對詢問按照如下的方式排序:以左端點所在的塊升序為第一關鍵字,以右端點升序為第二關鍵字。(注意:我們不能使用奇偶優化)
2. 遍歷所有分塊。
3. 我們先將莫隊當前分塊區間左端點初始化為分塊的右邊界+1,右端點初始化為分塊塊的右邊界,這是一個空區間。
4. 左右端點在同一個分塊中的詢問,我們直接暴力遍歷即可。然後再遍歷一次修改回原樣。
5. 左右端點不在同一個分塊中的詢問,一直擴充套件莫隊區間右端點直到區間右端點與詢問右端點重合。區間左端點每次都從當前分塊的右邊界開始往左邊擴充套件到左端點。然後再把區間左端點掃回分塊右邊界+1,把所有資料修改回原樣。(回滾)
6. 所有左端點在當前分塊內的詢問遍歷結束後,清空所有資料。(就是把區間右端點掃回分塊右邊界)(回滾)
下面,我們來看一道板子題:
洛咕 AT1219 歷史研究
題目描述
IOI國曆史研究的第一人——JOI教授,最近獲得了一份被認為是古代IOI國的住民寫下的日記。JOI教授為了通過這份日記來研究古代IOI國的生活,開始著手調查日記中記載的事件。
日記中記錄了連續N天發生的時間,大約每天發生一件。
事件有種類之分。第i天(1≤i≤N)發生的事件的種類用一個整數Xi表示,Xi越大,事件的規模就越大。
JOI教授決定用如下的方法分析這些日記:
1.選擇日記中連續的一些天作為分析的時間段
2.事件種類t的重要度為t*(這段時間內重要度為t的事件數)
3.計算出所有事件種類的重要度,輸出其中的最大值
現在你被要求製作一個幫助教授分析的程式,每次給出分析的區間,你需要輸出重要度的最大值。
輸入格式
第一行兩個空格分隔的整數N和Q,表示日記一共記錄了N天,詢問有Q次。 接下來一行N個空格分隔的整數X1...XN,Xi表示第i天發生的事件的種類 接下來Q行,第i行(1≤i≤Q)有兩個空格分隔整數Ai和Bi,表示第i次詢問的區間為[Ai,Bi]。
輸出格式
輸出Q行,第i行(1≤i≤Q)一個整數,表示第i次詢問的最大重要度
樣例
輸入樣例
5 5
9 8 7 8 9
1 2
3 4
4 4
1 4
2 4
輸出樣例
9
8
8
16
16
資料範圍與提示
1≤N≤105, 1≤Q≤105, 1≤Xi≤109 (1≤i≤N)
這題就是模版題,只是要注意兩點,一是離散化,二是開long long
不多說了,上程式碼 (可以當模板用,自認為碼風還是比較易懂的)
1 //回滾莫隊 2 //洛咕AT1219 歷史研究 3 4 #include <iostream> 5 #include <algorithm> 6 #include <cstdio> 7 #include <cstring> 8 #include <cmath> 9 #define ll long long 10 #define inf0x3f3f3f3f 11 using namespace std; 12 int read(){ 13 int ret=0,ttt=1; 14 char ch=getchar(); 15 while(ch<'0' || ch>'9'){ 16 if(ch=='-'){ 17 ttt=-1; 18 }ch=getchar(); 19 }while(ch>='0' && ch<='9'){ 20 ret=(ret<<1)+(ret<<3)+(ch^48); 21 ch=getchar(); 22 }return ret*ttt; 23 } 24 struct dat{ 25 int l,r,p; //詢問左端點、右端點、詢問編號(方便在排序後按輸入順序輸出答案) 26 }; 27 int n,m,sq; 28 int a[100005],pos[100005],num[100005],v[100005],b[100005]; //a 種類, b 存原來的a, v 離散值, pos[i] i屬於的分塊號, num 種類出現次數 29 ll ans[100005]; 30 dat no[100005]; //詢問 31 bool cmp(dat u, dat v){ 32 if(pos[u.l]==pos[v.l]){ 33 return u.r<v.r; 34 }return u.l<v.l; 35 } 36 int tt; 37 void in(){ //離散化(莫隊不需要,是由於題目原因) 38 sort(a+1,a+n+1); 39 tt=n; 40 tt=unique(a+1,a+tt+1)-(a+1); 41 for(int i=1; i<=n; i++){ 42 v[i]=lower_bound(a+1,a+tt+1,b[i])-a; 43 } 44 } 45 int main(){ 46 n=read(),m=read(); 47 sq=int(sqrt(n)); 48 int sum=0,cnt=0; 49 for(int i=1; i<=n; i++){ 50 a[i]=read(); 51 b[i]=a[i]; //離散化 52 if(i>sum){ 53 sum+=sq; 54 cnt++; 55 }pos[i]=cnt; 56 }in(); 57 for(int i=1; i<=m; i++){ 58 no[i].l=read(),no[i].r=read(); 59 no[i].p=i; 60 }sort(no+1,no+m+1,cmp); 61 int p=1; 62 for(int k=1; k<=n; k+=sq){ //遍歷所有塊 63 int you=min(k+sq-1,n),zuo=you+1; //莫隊區間左右端點 64 ll now=0,tmp=0; 65 for(int j=p; j<=m; j++){ 66 int l=no[j].l,r=no[j].r; 67 if(l>min(k+sq-1,n)){ 68 p=j; 69 break; 70 }if(j==m){ 71 p=m+1; 72 }if(pos[l]==pos[r]){ //特判:詢問左右端點在同一塊中 73 for(int i=l; i<=r; i++){ //暴力掃 74 num[v[i]]++; 75 now=max(now,1ll*b[i]*num[v[i]]); 76 }ans[no[j].p]=max(ans[no[j].p],now); 77 now=tmp; 78 for(int i=l; i<=r; i++){ 79 num[v[i]]--; //回滾 80 }continue; 81 }while(you<r){ //擴充套件右端點 82 you++; 83 num[v[you]]++; 84 now=max(now,1ll*b[you]*num[v[you]]); 85 }tmp=now; //tmp記錄此時的now值 86 while(zuo>l){ //擴充套件左端點 87 zuo--; 88 num[v[zuo]]++; 89 now=max(now,1ll*b[zuo]*num[v[zuo]]); 90 }ans[no[j].p]=max(ans[no[j].p],now); 91 now=tmp; //回滾now值 92 while(zuo<k+sq){ //回滾左端點 93 num[v[zuo]]--; 94 zuo++; 95 } 96 }while(you>k+sq-1){ //整個塊遍歷結束,回滾右端點 97 num[v[you]]--; 98 you--; 99 } 100 }for(int i=1; i<=m; i++){ 101 printf("%lld\n",ans[i]); 102 } 103 return 0; 104 }