1. 程式人生 > 實用技巧 >線段樹分治學習筆記

線段樹分治學習筆記

線段樹分治學習筆記

思想

對於下面這樣一類問題

在一個時間軸上,有一系列有時間區間的操作,且有詢問某個時間點上所有操作的貢獻的一些詢問

我們可以採用線段樹分治,就是在時間軸上建立一棵線段樹,把操作放線上段樹的區間上,然後遍歷線段樹,執行操作,統計貢獻,當遞迴到葉子節點時回答詢問,回溯時再撤銷操作.

最開始聽不會太懂,做幾道題基本上就會懂了


例題

線段樹分治上維護資料結構有兩個思路,一個是利用操作序變成樹上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;
}

尾聲

其實我感覺打打題基本上就明白了吧