算法 {莫队}
莫隊算法
性質
#排序比較函數, 必須是全序的#
比如你單個查詢的參數有X個, 那麽做法是把前X-1
個參數 進行分塊, 然後對最後一個參數 直接排序 不要進行分塊;
@DELI;
錯誤
#其實是正確的#: 右端點(即第二排序關鍵字) 不用分塊 直接排序即可;
也就是if( 左端點是同一個塊内){ return 右端點}
而不是return 右端點分塊后的塊號;
, 否則會產生以下錯誤:
0: 你的比較函數 不是全序的 而是偏序的, 這不符合莫隊算法的規定;
1: 時間會大大降低; 對於l
在同一個塊裏的查詢們(假設有C個) 他們的r
端點 會一直在震蕩(震蕩長度是塊長) 此時r
端點的移動次數是C*塊長
; 而如果直接對r
端點排序(不分塊) 那麽時間是N(數組長度)
;
@DELI;
莫队算法时间本身就比较极限, 请确保Add,Delete
函数的效率是O(1)
的, 否则的话 即使是O(logN)
也是会超时的 参见@LINK: 题解
;
數組區間查詢
定義
#問題#: 參見例題@LINK: https://www.acwing.com/problem/content/description/2494/
; 對於長度為N的數組A, 共有M個查詢 每次查詢A[l...r]
這個區間的信息;
#算法#: 將所有查詢做離散化處理 即進行排序, 排序規則為: 對於[l,r]
, 第一權重:[左端點進行分塊 按照塊號b_l
遞增], 第二權重:[如果b_l是偶數
則按照r
遞增, 否則遞減];
性質
#右端點 為什麼要奇偶相間#
比如數組長度N, 分塊長度為x
, 即一共分為[0,1,...N/x)
個塊;
第0
塊 是按照r
遞增的, 即你遍歷完所有左端點在0
塊的區間查詢後, 此時r
端點會非常的大;
到第1
塊時 此時讓r
遞減, 這樣就和上一次的r
就拼接對應了, 就會讓r
的移動 變小;
時間為: 右端點移動次數 + 左端點移動次數 = N/x(塊個數,表示左端點在同一個塊内的查詢) * N(左端點在同一塊內的右端點的移動次數) + M(查詢次數) * x(每次查詢 左端點都是移動x)
;
解這個方程 N*N/x + M*x
, 他倆的乘積是個定值 而且N*N/x, M*x > 0
, 所以根據算術-幾何不等式 (N*N/x + M*x)/2 >= sqrt(N*N/x * M*x)
, 當N*N/x == M*x
時 此時會讓他倆的和 最小, 因此我們讓x = sqrt(N*N/M)
, 此時時間複雜度是N * sqrt(M)
;
@DELI;
錯誤
#Delete
元素時 此時該元素 不一定存在於所維護的集合裡, 即此時她的Cont
不一定是>0
的;#
舉個例子, [3,4] -> [6,7]
他倆在同一塊 且這個塊是r
遞增, 先移動l
端點 即把[3,4,5]
給刪除掉 但5
是不存在的 因此會導致Cont[5] = -1
, 等到移動r
時 會把5
再給補回來;
.
你可能認為 那我先移動r
端點? 也不行, 比如[6,7] -> [3,4]
這個塊是r
遞減的, 那麼你會把[7,6,5]
給刪除 同樣會導致Cont[5] = -1
;
代碼模板
namespace ___MoTao_Queue{
vector<int> __Array; // 原數組;
vector< pair<int,int> > __Querys; // 她的次序不會變 (即與題目查詢的次序保持一致);
vector<int> __Order;
vector<int> ANS; // `ANS[i]`表示第`i`查詢對應的答案;
namespace __CurrentInfo{
int ANS;
int Cont[ ?]; // 所有`Array`的元素 必須是可以放到下標裏面;
void Add( int _ind){
++ Cont[ __Array[_ind]]; if( Cont[ __Array[_ind]] == 1){; ++ ANS;}
}
void Delete( int _ind){
-- Cont[ __Array[_ind]]; if( Cont[ __Array[_ind]] == 0){ -- ANS;}
}
} // namespace __CurrentInfo
void Initialize(){
@TODO( 檢查錄入格式是否合法);
int N; cin>> N;
__Array.resize( N); for( auto & i : __Array){ cin>> i;}
int M; cin>> M;
__Querys.resize( M); for( auto & i : __Querys){ cin>> i.first>> i.second; --i.first, --i.second;}
}
void Work(){
int __blockLength = std::sqrt( ((Double)__Array.size() * __Array.size() / __Querys.size()).v); __blockLength = max( __blockLength, 1);
__Order.resize( __Querys.size()); std::iota( __Order.begin(), __Order.end(), 0);
std::sort( __Order.begin(), __Order.end(), [&]( int _a, int _b)->bool{
auto block0 = __Querys[_a].first / __blockLength;
auto block1 = __Querys[_b].first / __blockLength;
if( block0 != block1){ return block0 < block1;}
if( block0 & 1){ return __Querys[_a].second > __Querys[_b].second;}
return __Querys[_a].second < __Querys[_b].second;
});
//{ __CurrentInfo
__CurrentInfo::ANS = 0;
memset( __CurrentInfo::Cont, 0, sizeof( __CurrentInfo::Cont));
//} __CurrentInfo
ANS.resize( __Querys.size());
int cl = __Querys[ __Order.front()].first, cr = cl-1;
for( auto ord : __Order){
int tl = __Querys[ ord].first, tr = __Querys[ ord].second;
while( cl < tl){ __CurrentInfo::Delete( cl ++);}
while( cl > tl){ __CurrentInfo::Add( -- cl);}
while( cr < tr){ __CurrentInfo::Add( ++ cr);}
while( cr > tr){ __CurrentInfo::Delete( cr --);}
ANS[ ord] = __CurrentInfo::ANS;
}
}
} // namespace ___MoTao_Queue
例題
@LINK: https://editor.csdn.net/md/?not_checkout=1&articleId=135573017
;
查詢子區間的不同元素個數
@DELI;
@LINK: https://editor.csdn.net/md/?not_checkout=1&articleId=136464598
;
這個題 如果用莫隊 是會超時的…
數組區間查詢+單點修改
定義
@LINK: https://www.acwing.com/problem/content/description/2523/
;
代碼
namespace ___MoTao_Queue{
vector<int> __Array; // 原數組;
vector< tuple<int,int,int> > __Modifys; // {下標,修改前的值,修改后的值}
vector< tuple<int,int,int> > __Querys; // {左端點,右端點,修改時間} (比如令`t:修改時間` 他表示執行`Modifys[0...t]`這些修改后的數組,因此`-1<=t<Modifys.size()`); 她的次序不會變 (即與題目查詢的次序保持一致);
vector<int> __Order;
vector<int> ANS; // `ANS[i]`表示第`i`查詢對應的答案;
namespace __CurrentInfo{
int ANS;
int Cont[ 1000006]; // 所有`Array`的元素 必須是可以放到下標裏面;
void Add( int _v){ ++ Cont[_v]; if( Cont[_v] == 1){; ++ ANS;}}
void Delete( int _v){ -- Cont[_v]; if( Cont[_v] == 0){ -- ANS;}}
} // namespace __CurrentInfo
void Initialize(){
int N; cin>> N;
int M; cin>> M; // 操作個數(查詢加上修改的總和 即`__Modifys.size() + __Querys.size() == M`);
__Array.resize( N); for( auto & i : __Array){ cin>> i;}
__Querys.clear(); __Modifys.clear();
string op; int d0, d1;
for( int m = 0; m < M; ++m){
cin>> op>> d0>> d1;
if( op == "Q"){
-- d0, -- d1;
__Querys.emplace_back( d0, d1, int(__Modifys.size()) - 1);
}
else{
-- d0;
__Modifys.emplace_back( d0, __Array[d0], d1);
__Array[d0] = d1;
}
}
}
void Work(){
int __blockLength = std::sqrt( __Array.size()); __blockLength = max( __blockLength, 1);
__Order.resize( __Querys.size()); std::iota( __Order.begin(), __Order.end(), 0);
std::sort( __Order.begin(), __Order.end(), [&]( int _a, int _b)->bool{
int la = std::get<0>(__Querys[_a])/ __blockLength;
int lb = std::get<0>(__Querys[_b])/ __blockLength;
if( la != lb){ return la < lb;}
int ra = std::get<1>(__Querys[_a])/ __blockLength;
int rb = std::get<1>(__Querys[_b])/ __blockLength;
if( ra != rb){ return ra < rb;}
return std::get<2>(__Querys[_a]) < std::get<2>(__Querys[_b]);
});
//{ __CurrentInfo
__CurrentInfo::ANS = 0;
memset( __CurrentInfo::Cont, 0, sizeof( __CurrentInfo::Cont));
//} __CurrentInfo
ANS.resize( __Querys.size());
int cl = std::get<0>(__Querys[ __Order.front()]), cr = cl-1, ct = int(__Modifys.size())-1;
for( auto ord : __Order){
int tl = std::get<0>(__Querys[ ord]), tr = std::get<1>(__Querys[ ord]), tt = std::get<2>(__Querys[ord]);
while( ct < tt){
int ind = std::get<0>(__Modifys[ct+1]), oldV = std::get<1>(__Modifys[ct+1]), newV = std::get<2>(__Modifys[ct+1]);
++ ct; __Array[ ind] = newV;
if( ind>=cl && ind<=cr){ __CurrentInfo::Delete( oldV); __CurrentInfo::Add( newV);}
}
while( ct > tt){
int ind = std::get<0>(__Modifys[ct]), oldV = std::get<1>(__Modifys[ct]), newV = std::get<2>(__Modifys[ct]);
-- ct; __Array[ ind] = oldV;
if( ind>=cl && ind<=cr){ __CurrentInfo::Delete( newV); __CurrentInfo::Add( oldV);}
}
while( cl < tl){ __CurrentInfo::Delete( __Array[cl]); ++cl;}
while( cl > tl){ __CurrentInfo::Add( __Array[cl-1]); --cl;}
while( cr < tr){ __CurrentInfo::Add( __Array[cr+1]); ++cr;}
while( cr > tr){ __CurrentInfo::Delete( __Array[cr]); --cr;}
ANS[ ord] = __CurrentInfo::ANS;
}
}
} // namespace ___MoTao_Queue
錯誤
@LINK: https://editor.csdn.net/md/?articleId=136464598
;
這個題 不能用莫隊, 因爲你會導致Add/Delete
函數 不是O(1)
;
筆記
中國算法選手莫涛所發明的;
排序時:
@IF( 奇數則右端點遞減): 效率會高 (因為右端點會相近 左端點不用管), 但會導致一個問題: Delete
時 她可能不存在, 比如同一個塊內[4,6] -> [2,3]
;