離線算法

整體二分搜、莫隊、操作分治

何謂離線?

前一個詢問的答案不影響下一個操作的內容

在線

離線

Trajan LCA

doubling LCA

莫隊演算法

已知區間\([l,r]\)的答案時,可以快速求出

\([l,r\pm1]\)、\([l\pm1,r]\)、的答案

可以快速地維護加入或刪除一個元素後的狀態

  1. 將序列每 \(k\) 個分成一塊,共有 \(\lceil\frac{n}{k}\rceil\) 塊

  2. 將詢問分到其左界所在的block

    • \(Q_i=(L_i,R_i)\in \text{block}_{\lfloor\frac{L_i}{k}\rfloor}\)

  3. 塊內的詢問按照右界排序

  1. 塊內的詢問
    1. 左界會在塊內左右跳動
    2. 右界遞增
  2. 每一塊內的右界都遞增:
    • \(O(\frac{n}{k}\times n\times a)\)
  3. 每跳到下一個詢問左界最多移動 \(k\) 個元素
    • \(O(q\times k\times a)\)
  4. 總複雜度:\(O(a)\times O(\frac{n^2}{k}+qk)\)
  5. \(k=\frac{n}{\sqrt{q}}\) 時有最佳解 \(O(n\sqrt{q})\)

Why

假設增加/刪除一個元素需要 \(O(a)\) 的時間

struct question{int l,r,i;}; // 第i個詢問 [l,r]
int n,q,k; vector<int> arr;
void init(int n){}
void add(int i){}
void rem(int i){}
int get_ans()
signed main(){
    vector<question> querys;
    cin>>n>>q;
    arr.resize(n);
    querys.resize(q);
    k=n/sqrt(q);
    for(int i=0;i<n;i++) cin>>arr[i];
    for(int i=0;i<q;i++)
        cin>>querys[i].l>>querys[i].r,querys[i].i=i;
    sort(querys.begin(),querys.end(),
        [](question a,question b){
            return a.l/k<b.l/k||(a.l/k==b.l/k&&a.r<.r);
        });
    init(n);
    int l=0,r=-1; vector<int> ans(q);
    for(int i=0;i<q;i++){
        while(r<querys[i].r) add(++r);
        while(querys[i].l<l) add(--l);
        while(querys[i].r<r) rem(r--);
        while(l<querys[i].l) rem(l++);
        ans[querys[i].i]=get_ans();
    }
    return 0;
}

ZJ b417

給定一個長度為 \(n\) 的正整數序列 \(s\),

對於 \(m\) 次詢問 \((l,r)\),每次輸出 \([s_l,\dots,s_r]\)

中眾數的個數以及有幾種數字是眾數。

\(n,m\leq10^5,1\leq s_i\leq n\)。

維護"每個數字出現幾次"

和"每個出現次數出現幾次"

兩者都可以用map實現

map<int,int> cnt,cntofcnt;
void init(){}
void decrease(map<int,int> &m,int v){
    if(--m[v]==0) m.erase(v);
}
void add(int i){
    decrease(cntofcnt,cnt[arr[i]]);
    cnt[arr[i]]++;
    cntofcnt[cnt[arr[i]]]++;
}
void rem(int i){
    decrease(cntofcnt,cnt[arr[i]]);
    decrease(cnt,arr[i]);
    cntofcnt[cnt[arr[i]]]++;
}
pii get_ans(){return *cntofcnt.rbegin();}

給定長度為 \(n\) 的序列及 \(q\) 筆詢問 \((L_i,R_i,K_i)\),求出每筆詢問對於區間 \([L_i,R_i]\),是否存在一個數字出現大於等於 \(\frac{R_i-L_i+1}{K_i}\) 次。\((n\leq5\times10^4)\)

vector<int> cnt,cntofcnt;
void init(int t){cnt.resize(t);cntofcnt.resize(n+2);cntofcnt[0]=t;}
void add(int i){
    cntofcnt[cnt[arr[i]]]--;
    cnt[arr[i]]++; cntofcnt[cnt[arr[i]]]++;
    if(cntofcnt[maxx+1]) maxx++;
}
void rem(int i){
    cntofcnt[cnt[arr[i]]]--;
    cnt[arr[i]]--; cntofcnt[cnt[arr[i]]]++;
    if(cntofcnt[maxx]==0) maxx--;
}
bool get_ans(question &q){return maxx;}
int get_rank(vector<int> &v,int x){
    return lower_bound(v.begin(),v.end(),x)-v.begin();
}
signed main(){
    mapping=arr; sort(mapping.begin(),mapping.end());
    mapping.erase(unique(mapping.begin(),mapping.end()),mapping.end());
    for(auto &i:arr) i=get_rank(mapping,i);
}

更優化

arr中元素值域壓縮後改用陣列來計數

  • 刪除元素時眾數最多減一

操作分治(CDQ)

divide and conquer (?)

  • 將所有操作依時間分成前半和後半
  • 遞迴計算前半和後半操作的答案
  • 計算前半操作對後半操作的影響&答案

\(T\)

1

2

3

前半   後半

逆序數對 TIOJ 1081

  • 值域壓縮 + BIT
  • merge sort
int ans=0,n; vector<int> v;
void solve(int l,int r){
    if(r-l==1) return void();
    int mid=(l+r)/2;
    solve(l,mid); solve(mid,r);
    vector<int> temp(r-l);
    int pl=l,pr=mid,cnt=0;
    for(int i=0;i<r-l;i++){
        if(pl==mid) temp[i]=v[pr++],cnt++;
        else if(pr==r) temp[i]=v[pl++],ans+=cnt;
        else if(v[pl]<=v[pr]) temp[i]=v[pl++],ans+=cnt;
        else temp[i]=v[pr++],cnt++;
    }
    for(int i=l;i<r;i++) v[i]=temp[i-l];
}

一個二維平面上,有 \(n\) 次操作,

\((1,x,y,w)\) 代表在座標 \((x,y)\) 加上權重 \(w\) ,

\((2,x,y)\) 代表詢問座標 \((x,y)\) 左下角的權重總和。

\((n\leq10^5\text{,}|x|,|y|\leq10^9)\)。

No Judge

假如用操作分治,考慮:

  • 遞迴計算前半的操作內互相影響(紅\(\rightarrow\)紅)和後半的操作內互相影響(綠\(\rightarrow\)綠)
  • 只需要考慮前半的修改如何影響後半的詢問(紅\(\rightarrow\)綠)

\(T\)

計算前半修改對後半詢問的影響

將操作由 \(x\) 座標小的開始做

若遇到前半的修改,加進BIT

若遇到後半的查詢,查詢BIT

#include <bits/stdc++.h>
#define ericxiao cin.tie(0); ios_base::sync_with_stdio(false);
#define endl '\n'
// #define int long long
using namespace std;
typedef pair<int,int> pii;
struct BIT{
    vector<int> v; vector<pii> rec; int n;
    int lb(int x){return x&(-x);}
    void init(int nn){n=nn+1;v.clear();v.resize(n);}
    void mod(int i,int x,bool record=true){
        if(record) rec.push_back({i,x});
        while(i<=n) v[i]+=x,i+=lb(i);
    }
    int que(int i){int t=0;while(i>0) t+=v[i],i+=lb(i);return t;}
    int que(int l,int r){return que(r)-que(l-1);}
    void undo(){
        while(rec.size())
            mod(rec.back().first,rec.back().second,false),rec.pop_back();
    }
} bit;
struct action{char type;int x,y,w,i;};
int n; vector<action> act; vector<int> ans;
void solve(int l,int r){
    if(r-l==1) return void();
    int mid=(l+r)/2;
    solve(l,mid); solve(mid,r);
    vector<action> temp(r-l);
    int pl=l,pr=mid,cnt=0;
    for(int i=0;i<r-l;i++){
        if(pl==mid){
            if(act[pr].type=='q') ans[act[pr].i]+=bit.que(act[pr].y);
            temp[i]=act[pr++];
        }else if(pr==r){
            if(act[pr].type=='m') bit.mod(act[pl].y,act[pl].w);
            temp[i]=act[pl++];
        }else if(act[pl].x<=act[pr].x){
            if(act[pr].type=='m') bit.mod(act[pl].y,act[pl].w);
            temp[i]=act[pl++];
        }else{
            if(act[pr].type=='q') ans[act[pr].i]+=bit.que(act[pr].y);
            temp[i]=act[pr++];
        }
    }
    for(int i=l;i<r;i++) act[i]=temp[i-l];
}
int get_rank(vector<int> &v,int x){return lower_bound(v.begin(),v.end(),x)-v.begin()+1;}
signed main(){
    int t; cin>>n; act.resize(n);
    vector<int> mapx,mapy;
    for(int i=0;i<n;i++){
        cin>>t;
        if(t==1){
            act[i].type='m';
            cin>>act[i].x>>act[i].y>>act[i].w;
        }else{
            act[i].type='q';
            cin>>act[i].x>>act[i].y;
            ans.push_back(0);
        }
        mapy.push_back(act[i].y);
    }
    mapy.erase(unique(mapy.begin(),mapy.end()),mapy.end());
    for(auto &i:act) i.y=get_rank(mapy,i.y);
    bit.init(mapy.size()); solve(0,n);
    for(auto &i:ans) cout<<i<<endl;
    return 0;
}

整體二分搜

整體二分搜

整體一起二分搜

正常二分搜時:

  1. 將區間切兩半
  2. 查看該往左邊還是右邊走
  3. 走下去,Repeat

整體二分搜時:

  1. 將區間切兩半
  2. 查看"每個元素""每筆詢問"該往左邊還是右邊走
  3. 走下去,Repeat

Why

  • "查看往左或往右"時,一起做可能比較快

給定長度為 \(n\) 的序列及 \(q\) 個操作。

操作 \((1,L,R,K)\) 代表詢問區間 \([L,R]\) 的第 \(K\) 大數字

(\(\texttt{sort}\)後的第 \(K\) 個元素)。

操作 \((2,i,K)\) 代表將第 \(i\) 格的數字改成 \(K\)。

操作 \((3,L,R)\) 請輸出 \(7122\)

struct action{char type; int l,r,i,k;};
// 修改:type='q', i=修改位置, k=修改後的值
// 查詢:type='m', [l,r]=修改範圍, i=第i筆詢問
struct BIT{
    vector<int> v; vector<pii> rec; int n;
    void init(int nn){n=nn; v.clear(); v.resize(n+1);}
    int lb(int x){return x&(-x);}
    void mod(int i,int x=1,int record=true){
        if(record) rec.push_back({i,x});
        while(i<=n) v[i]+=x,i+=lb(i);
    }
    int que(int i){int t=0; while(i>0) t+=v[i],i-=lb(i); return t;}
    int que(int l,int r){return que(r)-que(l-1);}
    void undo(){
        while(rec.size())
            mod(rec.back().first,-rec.back().second,false),rec.pop_back();
    }
} bit;
vector<action> act; vector<int> ans;
void total_binary_search(vector<int> &ind,int l,int r){
    if(l+1==r||ind.size()==0){
        for(auto &i:ind) if(act[i].type=='q') ans[act[i].i]=l;
        return void();
    }
    int mid=(l+r)/2;
    vector<int> L,R;
    for(auto &i:ind){
        if(act[i].type=='q'){
            int k=bit.que(act[i].l,act[i].r);
            if(k>=act[i].k)
                L.push_back(i);
            else
                act[i].k-=k,R.push_back(i);
        }else{
            if(act[i].i>0){
                if(act[i].k<mid)
                    bit.mod(act[i].i),L.push_back(i);
                else
                    R.push_back(i);
            }else{
                if(act[i].k<mid)
                    bit.mod(-act[i].i,-1),L.push_back(i);
                else
                    R.push_back(i);
            }
        }
    }
    // cout<<l<<' '<<mid<<' '<<r<<endl;
    // cout<<"L: "; for(auto &i:L) cout<<i<<' '; cout<<endl;
    // cout<<"R: "; for(auto &i:R) cout<<i<<' '; cout<<endl;
    bit.undo();
    total_binary_search(L,l,mid);
    total_binary_search(R,mid,r);
}
signed main(){
    int t,n,m,k; cin>>t;
    while(t--){
        cin>>n>>m; act.clear();
        bit.init(n); ans.clear();
        vector<int> arr={0};
        for(int i=0;i<n;i++){
            act.push_back(action());
            cin>>act[i].k; act[i].i=i+1;
            act[i].type='m'; arr.push_back(act[i].k);
        }
        for(int i=0;i<m;i++){
            cin>>k;
            if(k==1){
                act.push_back(action()); act.back().type='q';
                act.back().i=ans.size(); ans.push_back(0);
                cin>>act.back().l>>act.back().r>>act.back().k;
            }else if(k==2){
                act.push_back(action()); act.back().type='m';
                cin>>k; act.back().i=-k; act.back().k=arr[k];
                cin>>arr[k]; act.push_back(act.back());
                act.back().i=k; act.back().k=arr[k];
            }else cin>>k>>k,ans.push_back(7122);
        }
        // for(auto &i:act)
        //     cout<<i.type<<' '<<i.l<<' '<<i.r<<' '<<i.i<<' '<<i.k<<endl;
        vector<int> v; for(int i=0;i<act.size();i++) v.push_back(i);
        total_binary_search(v,-inf,inf);
        for(auto &i:ans) cout<<i<<endl;
    }
    return 0;
}

帶修改整體二分

Made with Slides.com