算法 {莫队}

算法 {莫队}

莫隊算法

性質

#排序比較函數, 必須是全序的#
比如你單個查詢的參數有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];

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值