線段樹分治學習筆記
阿新 • • 發佈:2020-10-18
線段樹分治學習筆記
思想
對於下面這樣一類問題
在一個時間軸上,有一系列有時間區間的操作,且有詢問某個時間點上所有操作的貢獻的一些詢問
我們可以採用線段樹分治,就是在時間軸上建立一棵線段樹,把操作放線上段樹的區間上,然後遍歷線段樹,執行操作,統計貢獻,當遞迴到葉子節點時回答詢問,回溯時再撤銷操作.
最開始聽不會太懂,做幾道題基本上就會懂了
例題
線段樹分治上維護資料結構有兩個思路,一個是利用操作序變成樹上dfs序也就是棧序,使用可撤銷資料結構;另一個是利用樹深度有限多次複用資料結構,具體來說就是維護 log 個數據結構,每次往下走都從父親那裡拷貝一份過來
by 夏色祭Official in luogu
不過現在只刷了可撤銷並查集呢
1 P5787 二分圖 /【模板】線段樹分治
題目大意是在長度為k的時間段裡,n個點的圖上有m條邊,其中邊 \(i\) 的存在時間為\(l_i-r_i\) ,求每個單位時間內圖是否為二分圖
判斷圖是否為二分圖就是判斷有沒有奇環,這個可以用並查集以類似於食物鏈的方法來解決。在時間軸上建立並查集,對於存在一段時間的邊,我們可以把它掛在線段樹\(l_i-r_i\)的一些最大組成節點上,在回溯時用可撤銷的並查集(按秩合併)來撤銷加的邊,走到一個節點判斷圖是否為奇環,最後到葉子節點輸出答案即可。
注意若在一個節點已判斷有奇環,則其子節點不用在進行操作,否則被 hack 掉
結合程式碼理解
#include<bits/stdc++.h> using namespace std; int const MAXN=2e5+10; int n,m,k,cnt,tot,tott,_; int f[MAXN],siz[MAXN]; vector<int>seg[MAXN<<2]; stack<int>st; struct op{ int u,v,be,en; }e[MAXN]; void change(int x,int l,int r,int L,int R,int a){ if(l<=L && R<=r){ seg[x].push_back(a); return; } int mid=(L+R)>>1; if(l<=mid)change(x<<1,l,r,L,mid,a); if(r>mid)change(x<<1|1,l,r,mid+1,R,a); return ; } int get(int x){ while(x!=f[x])x=f[x]; return x; } bool merge(int a,int b){ bool flag=1; int fa=get(a),fb=get(b),fan=get(a+n),fbn=get(b+n); if(fa==fan || fb==fbn || fa==fb || fan==fbn)return 0; if(fa!=fbn){ if(siz[fa]>siz[fbn])swap(fa,fbn); f[fa]=fbn;siz[fbn]+=siz[fa];st.push(fa); } if(fb!=fan){ if(siz[fb]>siz[fan]) swap(fb,fan); f[fb]=fan;siz[fan]+=siz[fb];st.push(fb); } return 1; } void del(int sum){ while(sum<st.size()){ int x=st.top();st.pop(); siz[f[x]]-=siz[x];f[x]=x; } return; } void query(int x,int L,int R,bool flag){ int sum=st.size(); for(int i=0;i<seg[x].size();i++){ if(!flag)break; int a=e[seg[x][i]].u,b=e[seg[x][i]].v; if(!merge(a,b))flag=0; } if(L==R){ if(flag)printf("Yes\n"); else printf("No\n"); del(sum); return; } int mid=(L+R)>>1; query(x<<1,L,mid,flag);query(x<<1|1,mid+1,R,flag); del(sum); return; } int main(){ scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=2*n;i++)f[i]=i,siz[i]=1; for(int i=1;i<=m;i++){ int x,y,l,r; scanf("%d%d%d%d",&x,&y,&l,&r); if(l==r)continue; e[i]=(op){x,y,l+1,r}; change(1,l+1,r,1,k,i); } query(1,1,k,1); return 0; }
2 P5214 [SHOI2014]神奇化合物
跟例1差不多的思路,挺板的。把邊掛在線段樹上,用可撤銷的並查集維護分子關係
#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<cstdio>
#include<vector>
#include<climits>
#include<string>
#include<deque>
#include<bitset>
#include<cstring>
#include<stack>
#include<cstdlib>
#include<iostream>
#include<ctime>
#include<algorithm>
using namespace std;
int const MAXN=4e5+10;
int n,m,tot,tott,cnt,_,q,ans;
int e[5001][5001],f[MAXN],siz[MAXN];
vector<int>seg[MAXN*4];
stack<int>sta;
struct edge{
int a,b,st,en;
}op[MAXN];
int get(int x){
while(x!=f[x])x=f[x];
return x;
}
void insert(int x,int l,int r,int L,int R,int id){
if(l<=L && R<=r){
seg[x].push_back(id);
return;
}
int mid=(L+R)>>1;
if(l<=mid)insert(x<<1,l,r,L,mid,id);
if(r>mid)insert(x<<1|1,l,r,mid+1,R,id);
return ;
}
void merge(int a,int b){
int fa=get(a),fb=get(b);
if(fa!=fb){
if(siz[fa]>siz[fb])swap(fa,fb);
f[fa]=fb;siz[fb]+=siz[fa];
sta.push(fa);ans--;
}
}
void del(int sum){
while(sum<sta.size()){
int x=sta.top();sta.pop();
ans++;
siz[f[x]]-=siz[x];f[x]=x;
}
}
void query(int x,int L,int R){
int sum=sta.size();
for(int i=0;i<seg[x].size();i++){
int a=op[seg[x][i]].a,b=op[seg[x][i]].b;
merge(a,b);
}
if(L==R){
printf("%d\n",ans);
del(sum);
return;
}
int mid=(L+R)>>1;
query(x<<1,L,mid);query(x<<1|1,mid+1,R);
del(sum);
return;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
f[i]=i,siz[i]=1;
}
for(int i=1;i<=m;i++){
cnt++;
scanf("%d%d",&op[cnt].a,&op[cnt].b);
e[op[cnt].a][op[cnt].b]=e[op[cnt].b][op[cnt].a]=cnt;
op[cnt].st=1,op[cnt].en=0;
}
scanf("%d",&q);
for(int i=1;i<=q;i++){
char c[10];int a,b;
scanf("%s",c+1);
if(c[1]=='A'){
scanf("%d%d",&a,&b);
e[a][b]=e[b][a]=++cnt;
op[cnt].a=a,op[cnt].b=b;
op[cnt].st=tot+1,op[cnt].en=0;
}else if(c[1]=='D'){
scanf("%d%d",&a,&b);
op[e[a][b]].en=tot;
}else if(c[1]=='Q')tot++;
}
for(int i=1;i<=cnt;i++){
if(op[i].en==0)op[i].en=tot;
insert(1,op[i].st,op[i].en,1,tot,i);
}
ans=n;
query(1,1,tot);
return 0;
}
尾聲
其實我感覺打打題基本上就明白了吧