離線算法
整體二分搜、莫隊、操作分治
何謂離線?
前一個詢問的答案不影響下一個操作的內容
在線
離線
Trajan LCA
doubling LCA
莫隊演算法
已知區間\([l,r]\)的答案時,可以快速求出
\([l,r\pm1]\)、\([l\pm1,r]\)、的答案
可以快速地維護加入或刪除一個元素後的狀態
-
將序列每 \(k\) 個分成一塊,共有 \(\lceil\frac{n}{k}\rceil\) 塊
-
將詢問分到其左界所在的block
-
\(Q_i=(L_i,R_i)\in \text{block}_{\lfloor\frac{L_i}{k}\rfloor}\)
-
-
塊內的詢問按照右界排序
- 塊內的詢問
- 左界會在塊內左右跳動
- 右界遞增
- 每一塊內的右界都遞增:
- \(O(\frac{n}{k}\times n\times a)\)
- 每跳到下一個詢問左界最多移動 \(k\) 個元素
- \(O(q\times k\times a)\)
- 總複雜度:\(O(a)\times O(\frac{n^2}{k}+qk)\)
- \(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;
}
整體二分搜
整體二分搜
整體一起二分搜
正常二分搜時:
- 將區間切兩半
- 查看該往左邊還是右邊走
- 走下去,Repeat
整體二分搜時:
- 將區間切兩半
- 查看"每個元素"和"每筆詢問"該往左邊還是右邊走
- 走下去,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;
}
帶修改整體二分
offline_algo
By thomaswang2003
offline_algo
- 555