<更新提示>
<第一次更新> 基礎莫隊和帶修莫隊能夠看這個 課件。 c++
<正文>
基礎的莫隊算法相信你們都已經熟悉了,而咱們知道,莫隊算法的關鍵就在於如何進行區間的轉移,這就可能涉及到不少的細節。有一類普通莫隊不可解的問題就是在轉移區間過程當中,可能出現刪點或加點操做其中之一沒法實現的問題。那麼咱們就來探討如何利用特殊的莫隊算法來解決這類問題,而這種莫隊算法就稱之爲回滾莫隊算法。算法
咱們考慮一個區間問題,若這個問題在區間轉移中,加點操做得以實現,可是刪點操做沒法有效的實現時,就可使用以下的莫隊算法:數組
\(1.\) 對原序列進行分塊,並對詢問按照以下的方式排序:以左端點所在的塊升序爲第一關鍵字,以右端點升序爲第二關鍵字spa
\(2.\) 對於處理全部左端點在塊\(T\)內的詢問,咱們先將莫隊區間左端點初始化爲\(R[T]+1\),右端點初始化爲\(R[T]\),這是一個空區間code
\(3.\) 對於左右端點在同一個塊中的詢問,咱們直接暴力掃描回答便可。orm
\(4.\) 對於左右端點不在同一個塊中的全部詢問,因爲其右端點升序,咱們對右端點只作加點操做,總共最多加點\(n\)次排序
\(5.\) 對於左右端點不在同一個塊中的全部詢問,其左端點是可能亂序的,咱們每一次從\(R[T]+1\)的位置出發,只作加點操做,到達詢問位置便可,每個詢問最多加\(\sqrt n\)次。回答完詢問後,咱們撤銷本次移動左端點的全部改動,使左端點回到\(R[T]+1\)的位置事件
\(6.\) 按照相同的方式處理下一塊ip
根據其操做的過程可知,回滾莫隊的時間複雜度仍然爲\(O(n\sqrt n)\),而且,在回答詢問的過程當中咱們只進行了加點操做,沒有涉及刪點操做,這樣就完成了咱們須要的操做。rem
和上一種典型的回滾莫隊相似,咱們還能夠實現只有刪點操做沒有加點操做的回滾莫隊,固然,這樣的前提是咱們能夠正確的先將整個序列加入莫隊中,那麼算法流程以下:
\(1.\) 對原序列進行分塊,並對詢問按照以下的方式排序:以左端點所在的塊升序爲第一關鍵字,以右端點降序序爲第二關鍵字
\(2.\) 對於處理全部左端點在塊\(T\)內的詢問,咱們先將莫隊區間左端點初始化爲\(L[T]\),右端點初始化爲\(n\),這是一個大區間
\(3.\) 對於左右端點在同一個塊中的詢問,咱們直接暴力掃描回答便可。
\(4.\) 對於左右端點不在同一個塊中的全部詢問,因爲其右端點降序,從\(n\)的位置開始,咱們對右端點只作刪點操做,總共最多刪點\(n\)次
\(5.\) 對於左右端點不在同一個塊中的全部詢問,其左端點是可能亂序的,咱們每一次從\(L[T]\)的位置出發,只作刪點操做,到達詢問位置便可,每個詢問最多加\(\sqrt n\)次。回答完詢問後,咱們撤銷本次移動左端點的全部改動,使左端點回到\(L[T]\)的位置
\(6.\) 按照相同的方式處理下一塊
一樣地,回滾莫隊的時間複雜度仍是\(O(n\sqrt n)\),而且咱們只使用了刪點操做,只有在一開始時將整個序列加入到莫隊中,這樣就完成了咱們須要的操做。
那麼咱們將經過兩道例題來詳細地瞭解整兩種回滾莫隊。
IOI國曆史研究的第一人——JOI教授,最近得到了一份被認爲是古代IOI國的住民寫下的日記。JOI教授爲了經過這份日記來研究古代IOI國的生活,開始着手調查日記中記載的事件。
日記中記錄了連續N天發生的時間,大約天天發生一件。
事件有種類之分。第i天(1<=i<=N)發生的事件的種類用一個整數X_i表示,X_i越大,事件的規模就越大。
JOI教授決定用以下的方法分析這些日記:
第一行兩個空格分隔的整數N和Q,表示日記一共記錄了N天,詢問有Q次。
接下來一行N個空格分隔的整數X_1...X_N,X_i表示第i天發生的事件的種類
接下來Q行,第i行(1<=i<=Q)有兩個空格分隔整數A_i和B_i,表示第i次詢問的區間爲[A_i,B_i]。
輸出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
大體題意:給定一個長度爲\(n\)的序列,離線詢問\(m\)個問題,每次回答區間內元素權值乘以元素出現次數的最大值。
咱們考慮用莫隊來解決這個問題,顯然,爲了統計每一個元素的出現次數,咱們要用到桶。而加點操做就很好實現了,在桶中給元素的出現次數加一,並查看是否可以更新答案便可。可是刪點操做就難以實現,當咱們刪去一個點時,咱們難以得知新的最大值是多少,因此咱們用只加不減的回滾莫隊。
那麼回滾莫隊中提到的撤銷操做具體就是指在桶中減去出現次數,而無論答案是否改變。在下一次加點的過程當中,答案就得以統計了。
\(Code:\)
#include <bits/stdc++.h> using namespace std; const int N = 1e5+20 , SIZE = 1020; int n,m,size,T,raw[N],val[N],t,cnt[N],cnt_[N]; int belo[N],L[SIZE],R[SIZE]; long long ans[N],Max,a[N]; struct query{int l,r,id;}q[N]; inline void input(void) { scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) scanf("%lld",&a[i]) , raw[++t] = a[i]; for (int i=1;i<=m;i++) scanf("%d%d",&q[i].l,&q[i].r) , q[i].id = i; } inline void discrete(void) { sort( raw+1 , raw+t+1 ); t = unique( raw+1 , raw+t+1 ) - (raw+1); for (int i=1;i<=n;i++) val[i] = lower_bound( raw+1 , raw+t+1 , a[i] ) - raw; } inline void setblocks(void) { size = sqrt(n) , T = n/size; for (int i=1;i<=T;i++) { if ( i * size > n ) break; L[i] = (i-1) * size + 1; R[i] = i * size; } if ( R[T] < n ) T++ , L[T] = R[T-1] + 1 , R[T] = n; for (int i=1;i<=T;i++) for (int j=L[i];j<=R[i];j++) belo[j] = i; } inline bool compare(query p1,query p2) { if ( belo[p1.l] ^ belo[p2.l] ) return belo[p1.l] < belo[p2.l]; else return p1.r < p2.r; } // 加點 inline void insert(int p,long long &Maxval) { cnt[val[p]]++; Maxval = max( Maxval , 1LL * cnt[val[p]] * a[p] ); } // 撤銷 inline void resume(int p) { cnt[val[p]]--; } inline void CaptainMo(void) { sort( q+1 , q+m+1 , compare ); int l = 1 , r = 0 , lastblock = 0; for (int i=1;i<=m;i++) { // 處理同一塊中的詢問 if ( belo[q[i].l] == belo[q[i].r] ) { for (int j=q[i].l;j<=q[i].r;j++) cnt_[val[j]]++; long long temp = 0; for (int j=q[i].l;j<=q[i].r;j++) temp = max( temp , 1LL * cnt_[val[j]] * a[j] ); for (int j=q[i].l;j<=q[i].r;j++) cnt_[val[j]]--; ans[ q[i].id ] = temp; continue; } // 若是移動到了一個新的塊,就先把左右端點初始化 if ( lastblock ^ belo[q[i].l] ) { while ( r > R[belo[q[i].l]] ) resume(r--); while ( l < R[belo[q[i].l]]+1 ) resume(l++); Max = 0 , lastblock = belo[q[i].l]; } // 單調地移動右端點 while ( r < q[i].r ) insert(++r,Max); // 移動左端點回答詢問 long long temp = Max; int l_ = l; while ( l_ > q[i].l ) insert(--l_,temp); // 回滾 while ( l_ < l ) resume(l_++); ans[ q[i].id ] = temp; } } int main(void) { input(); discrete(); setblocks(); CaptainMo(); for (int i=1;i<=m;i++) printf("%lld\n",ans[i]); return 0; }
有一個長度爲n的數組{a1,a2,…,an}。m次詢問,每次詢問一個區間內最小沒有出現過的天然數。
第一行n,m。
第二行爲n個數。
從第三行開始,每行一個詢問l,r。
一行一個數,表示每一個詢問的答案。
5 5 2 1 0 2 1 3 3 2 3 2 4 1 2 3 5
1 2 3 0 3
這道題咱們莫隊是思路就是用桶維護出現過的數字,那麼\(mex\)值就是第一個不在桶中出現的數字。
咱們發現刪點操做很容易實現,能夠順帶的更新答案,可是加點操做難以實現,咱們原來的最小值在加點過程當中出現了,咱們就無從得知新的答案。顯然,一開始將整個序列加入到桶裏並統計答案是可行的,那麼咱們就是用只刪不加的回滾莫隊。
撤銷操做仍是在桶中更新,但無論答案的變化就能夠了。
\(Code:\)
#include <bits/stdc++.h> using namespace std; const int N = 200020 , SIZE = 1020; int n,m,a[N],cnt[N],Min,size,T,ans[N]; int cnt_[N],ans_,belo[N],L[N],R[N]; struct query{int l,r,id;}q[N]; inline void input(void) { scanf("%d%d",&n,&m); 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; } inline void init(void) { for (int i=1;i<=n;i++) if ( a[i] <= n+1 ) cnt[ a[i] ]++; while ( cnt[ ans_ ] ) ans_++; // 先把整個序列加入桶,同時獲得總體的答案 } inline void setblocks(void) { size = sqrt(n) , T = n/size; for (int i=1;i<=T;i++) { if ( i * size > n ) break; L[i] = (i-1)*size + 1; R[i] = i * size; } if ( R[T] < n ) T++ , L[T] = R[T-1] + 1 , R[T] = n; for (int i=1;i<=T;i++) for (int j=L[i];j<=R[i];j++) belo[j] = i; } inline bool compare(query p1,query p2) { if ( belo[p1.l] ^ belo[p2.l] ) return belo[p1.l] < belo[p2.l]; else return p1.r > p2.r; } // 刪點 inline void remove(int p,int &Minval) { if ( a[p] > n+1 ) return; cnt[a[p]]--; if ( cnt[a[p]] == 0 ) Minval = min( Minval , a[p] ); } // 撤銷 inline void resume(int p) { if ( a[p] > n+1 ) return; cnt[a[p]]++; } inline void CaptainMo(void) { sort( q+1 , q+m+1 , compare ); int l = 1 , r = n , lastblock = 0; for (int i=1;i<=m;i++) { // 處理同一塊中的詢問 if ( belo[q[i].l] == belo[q[i].r] ) { for (int j=q[i].l;j<=q[i].r;j++) if ( a[j] <= n+1 ) cnt_[a[j]]++; int temp = 0; while ( cnt_[temp] ) temp++; ans[ q[i].id ] = temp; for (int j=q[i].l;j<=q[i].r;j++) if ( a[j] <= n+1 ) cnt_[a[j]]--; continue; } // 若是移動到了一個新的塊,就先把左右端點初始化 if ( belo[q[i].l] ^ lastblock ) { while ( r < n ) resume(++r); while ( l < L[belo[q[i].l]] ) remove(l++,ans_); Min = ans_ , lastblock = belo[q[i].l]; } // 單調地移動右端點 while ( r > q[i].r ) remove(r--,Min); // 移動左端點回答詢問 int temp = Min , l_ = l; while ( l_ < q[i].l ) remove(l_++,temp); // 回滾 while ( l_ > l ) resume(--l_); ans[ q[i].id ] = temp; } } int main(void) { input(); init(); setblocks(); CaptainMo(); for (int i=1;i<=m;i++) printf("%d\n",ans[i]); return 0; }
<後記>