離線演算法入門——線段樹分治
阿新 • • 發佈:2021-07-07
離線演算法入門——線段樹分治
所謂線段樹分治是一種離線演算法,其主要思想是把時間當做序列,在上面建線段樹,每一個修改操作的影響範圍線上段樹上呈現,葉子節點就放詢問,這樣我們把所有詢問離線下來,就可以在 \(f(n)\log n\) 的複雜度內完成對所有詢問的回答。
線段樹分治相比於其他離線演算法來說,比較有優勢的一點是可撤銷性。即線段樹分治維護的是操作影響區間,這個是 CDQ 分治及其它離線演算法遠遠不能及的。
這些離線演算法都說的非常籠統,我們直接來看一道例題。
例題
我們對整個事件序列建線段樹,然後對線段樹的每一個節點建一個 vector
,表示在這個節點所代表時間區間裡所建的邊。
程式碼:
inline void change(int &k,int l,int r,int z,int y,int u,int v){ if(!k) k=++tot; if(l==z&&r==y){ p[k].bian.push_back(edge(u,v));return; } int mid=(l+r)>>1; if(y<=mid) change(p[k].l,l,mid,z,y,u,v); else if(z>mid) change(p[k].r,mid+1,r,z,y,u,v); else change(p[k].l,l,mid,z,mid,u,v),change(p[k].r,mid+1,r,mid+1,y,u,v); }
你會發現這就是線段樹的插入操作。
然後我們從根節點往下遍歷,每到一個節點把裡面的邊合併到圖上並判斷是否為二分圖,然後回溯的時候把所有加的邊去掉。
有沒有什麼資料結構能夠幫助我們快速的完成上面的那些操作呢?可撤銷擴充套件域並查集就可以做到這些。
擴充套件域並查集用來判二分圖,我們把每一個節點拆成兩個節點,表示兩個不同的集合,合併集合的時候注意要保證兩個節點不在一個集合。如果當前的兩個節點在同一集合的話,說明這不是二分圖。具體看程式碼實現。
而可撤銷並查集也並沒有這樣的神祕,我們只不過開一個棧把操作存進去罷了。
注意,因為要撤銷操作,不能壓縮路徑,且為了保證複雜度正確,需要按秩合併。
程式碼:
#include<bits/stdc++.h> #define dd double #define ld long double #define ll long long #define uint unsigned int #define ull unsigned long long #define N 700010 #define M number using namespace std; const int INF=0x3f3f3f3f; template<typename T> inline void read(T &x) { x=0; int f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c == '-') f=-f; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; x*=f; } int n,m,k; int fa[N],deep[N],top; pair<int,int> sta[N]; struct bingchaji{ inline int find(int a){ return a==fa[a]?a:find(fa[a]); } inline bool belong_to_the_same(int a,int b){ int faa=find(a),fab=find(b); return faa==fab; } inline bool merge(int a,int b){ int faa=find(a),fab=find(b); if(faa==fab) return 0; if(deep[faa]>deep[fab]) swap(faa,fab); fa[faa]=fab;sta[++top]=make_pair(faa,deep[fab]); if(deep[faa]==deep[fab]) deep[fab]++; return 1; } inline void chushihua(int n){ for(int i=1;i<=n;i++) fa[i]=i,deep[i]=0;top=0; } inline void chexiao(int k){ for(int i=1;i<=k;i++){ int lastdian=sta[top].first,lastdeep=sta[top].second; deep[fa[lastdian]]=lastdeep;fa[lastdian]=lastdian;top--; } } }; bingchaji bcj; struct edge{ int from,to; inline edge(){} inline edge(int from,int to) : from(from),to(to) {} }; struct node{ int l,r;vector<edge> bian; }; node p[N<<2]; int tot,root; bool ans[N]; inline void change(int &k,int l,int r,int z,int y,int u,int v){ if(!k) k=++tot; if(l==z&&r==y){ p[k].bian.push_back(edge(u,v));return; } int mid=(l+r)>>1; if(y<=mid) change(p[k].l,l,mid,z,y,u,v); else if(z>mid) change(p[k].r,mid+1,r,z,y,u,v); else change(p[k].l,l,mid,z,mid,u,v),change(p[k].r,mid+1,r,mid+1,y,u,v); } inline void solve(int &k,int l,int r,bool op){ if(!k) k=++tot; int mid=(l+r)>>1; int lasttop=top; for(int j=0;j<p[k].bian.size()&&op;j++){ edge nowbian=p[k].bian[j]; if(bcj.belong_to_the_same(nowbian.from,nowbian.to)){ op=0;break; } bcj.merge(nowbian.from,nowbian.to+n); bcj.merge(nowbian.to,nowbian.from+n); } if(l==r){ans[l]=op;} else if(op){solve(p[k].l,l,mid,op);solve(p[k].r,mid+1,r,op);} bcj.chexiao(top-lasttop); } int main(){ read(n);read(m);read(k); for(int i=1;i<=m;i++){ int x,y,l,r;read(x);read(y);read(l);read(r); change(root,0,k,l,r,x,y); } bcj.chushihua(n<<1); solve(root,0,k,1); for(int i=0;i<=k-1;i++) if(ans[i]||ans[i+1]) printf("Yes\n");else printf("No\n"); return 0; }
注意:在開 O2 的情況下儘量不要用區域性變數來計數,否則會 RE 的很慘。