1. 程式人生 > >[模板] 各種莫隊

[模板] 各種莫隊

pre 等價 seq math oid 維護 dfs inline 右移

簡介

對於一些區間查詢問題, 當詢問與數組大小同階時, 把詢問按塊排序, 可以得到均攤根號復雜度的算法.

普通莫隊

不含修改, 單點加入/刪除均較快 ( \(O(1)\)\(O(\log n)\) 等).

流程

  1. 區間大小 \(S = \sqrt n\)
  2. 排序:
    • \((\lceil \frac lS \rceil , r)\) 二元組排序
  3. 移動左右指針轉移

復雜度 \(O(n\sqrt n)\).

帶修改莫隊

可以單點較快修改.

流程

  1. 區間大小 \(S = n^{\frac 23}\)
  2. 排序:
    • 將詢問按 \((\lceil\frac l S\rceil, \lceil\frac r S\rceil, t)\)
      三元組排序
    • 修改不需排序
  3. 維護 \(l,r,t\) 三個指針轉移

復雜度 \(O(n^{\frac 53})\).

代碼

struct tq{int l,r,t,id;}q[qsz];
struct tc{int p,v;}c[qsz];
bool cmp(tq l,tq r){return inb[l.l]!=inb[r.l]?inb[l.l]<inb[r.l]:inb[l.r]!=inb[r.r]?inb[l.r]<inb[r.r]:l.t<r.t;}

void solp(int p){
    //do sth
}
void solc(int p,int c){
    //do sth
}

void mo(){
    sort(q+1,q+pq+1,cmp);
    int t=0,l=1,r=0;
    rep(i,1,pq){
        while(t<q[i].t)++t,solc(c[t].p,c[t].t);
        while(t>q[i].t)solc(c[t].p,c[t].v),--t;
        
        while(l<q[i].l)solp(seq[l++]);
        while(l>q[i].l)solp(seq[--l]);
        while(r<q[i].r)solp(seq[++r]);
        while(r>q[i].r)solp(seq[r--]);
        ans[q[i].id]=ans0;//update ans
    }
}

樹上莫隊

序列變成樹, 詢問子樹/鏈信息.

子樹

轉化成dfs序, 然後就變成區間信息了.

記錄歐拉序, 即每個點入和出各記錄一次,記為in(a), out(a).
考慮樹上的一個鏈 \([a,b]\), 不妨設in(a)<=in(b).
當a, b都不在另一個的子樹中時, 它等價於dfs序中的 \([out(a), in(b)] + lca(a,b)\) ,其中出現過兩次的點不統計;
當b在a的子樹中時, 它等價於dfs序中的 \([in(a), in(b)]\) ,其中出現過兩次的點不統計.
這樣就也轉化為了區間信息, 細節可能有所不同.

或者王室聯邦分塊... 不會

代碼/題目

luogu4074-[WC2013]糖果公園

只增莫隊

有時插入復雜度較小, 但刪除復雜度較大. 考慮只用插入實現.

流程

  1. 分塊 && 排序, 同普通莫隊.
  2. 若詢問在一個塊內, 直接暴力;
  3. 對於其他詢問: 枚舉塊 \([L_s,R_s]\), 處理左端點在該塊內的詢問:
    1. 起始左指針為 \(R_s + 1\), 右指針為 \(R_s\) ;
    2. 對於右指針, 根據排序, 同塊內詢問的右端點遞增, 右移指針, 維護加入點即可;
    3. 對於左指針, 對於每個詢問
      1. 保存當前的狀態
      2. 維護左指針向左移動, 加入點
      3. 詢問完成後恢復左指針到 \(R_s + 1\), 並恢復原來的狀態.

容易發現時間復雜度仍為 \(O(n\sqrt n)\).

[模板] 各種莫隊