1. 程式人生 > 實用技巧 >淺談建圖優化 - 線段樹優化

淺談建圖優化 - 線段樹優化

更新記錄

【1】2020.10.16-20:40

  • 1.完善內容

正文

在一些圖論題中,我們可能會遇到一些題目讓你對區間進行連邊

這個時候如果我們暴力進行連邊顯然是會超時的

我們就要考慮有沒有什麼辦法可以快速的進行區間連邊

這個時候就要祭出區間操作的神器 - 線段樹

線段樹上的非葉節點表示的是區間,我們就可以直接對他們進行連邊

舉個例子,我們連一條 \(2\) 與區間 \([3,5]\) 之間的雙向邊

首先進行建樹:

我們發現,如果只建一棵樹的話,會出現訪問錯誤
例如 \([mid+1,r]\) 會沿著區間錯誤的訪問到 \([l,r]\)

所以我們正反邊各建一棵線段樹:

然後讓葉節點沒有入度的那棵線段樹向節點 \(2\)

連一條單向邊,然後從節點 \(2\) 向沒有出度的那棵線段樹連一條單向邊

就像這樣:

之後我們按照原來的套路進行操作即可

CF786B Legacy

【題意簡化】

給你一個 \(n\) 個點的有向圖,圖上有 \(3\) 種邊

  • 1.\(u\to v\) 邊權為 \(w\)
  • 2.\(u\to[l,r]\) 邊權為 \(w\)
  • 3.\([l,r]\to u\) 邊權為 \(w\)

\(s\) 點到所有點的最短路

這道題還算是有些麻煩,對於第一種邊直接連線即可

第二種第三種就是將上面講的雙向邊的連線拆成單向邊進行連線

【程式碼實現】

#include<iostream>
#include<queue>
#include<cstring>
#define int long long
#define N 900004
#define M 5000050
using namespace std;
int n,q,s,num,head[N],dis[N],n2,n4,n8;
bool vis[N];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >que;
namespace in {int type,l,r,f,t,w;}
struct Edge {int na,np,w;} e[M];
struct segtree {int l,r;} t[N];
inline void add(int f,int t,int w=0){
	e[++num].na=head[f];
	e[num].np=t;e[num].w=w;
	head[f]=num;
}
inline void dbadd(int f,int t,int w=0) {add(f,t,w);add(t,f,w);}
inline void tadd(int p,int plus=0,bool s=0) {
	if(!s) add(p+plus,(p<<1)+plus),add(p+plus,(p<<1|1)+plus);
	else add((p<<1)+plus,p+plus),add((p<<1|1)+plus,p+plus);
}
inline void build(int p,int l,int r){
	t[p].l=l;t[p].r=r;
	if(l==r){
		dbadd(l+n8,p);
		dbadd(l+n8,p+n4);
		return;
	}
	tadd(p);tadd(p,n4,1);
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
}
inline void secadd(int p,int l,int r,int wp,int w,bool s){
	if(t[p].l==l&&t[p].r==r){
		if(!s) add(wp+n8,p,w);//s to p
		else add(p+n4,wp+n8,w);
		return;
	}
	int mid=(t[p].l+t[p].r)>>1;
	if(l<=mid) secadd(p<<1,l,r<mid?r:mid,wp,w,s);
	if(r>mid) secadd(p<<1|1,mid+1<l?l:mid+1,r,wp,w,s);
}
inline void dij(){
	memset(dis,0x7f,sizeof(dis));
	dis[s]=0;que.push(make_pair(0,s));
	while(!que.empty()){
		int p=que.top().second;que.pop();
		if(vis[p]) continue;
		vis[p]=1;
		for(int i=head[p];i;i=e[i].na){
			if(dis[e[i].np]>dis[p]+e[i].w){
				dis[e[i].np]=dis[p]+e[i].w;
				que.push(make_pair(dis[e[i].np],e[i].np));
			}
		}
	}
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>q>>s;
	n2=n<<1;n4=n<<2;n8=n<<3;
	build(1,1,n);
	using namespace in;
	for(int i=1;i<=q;i++){
		cin>>type;
		if(type==1){
			cin>>f>>in::t>>w;
			add(f+n8,in::t+n8,w);
		}
		else if(type==2){
			cin>>f>>l>>r>>w;
			secadd(1,l,r,f,w,0);
		}
		else{
			cin>>in::t>>l>>r>>w;
			secadd(1,l,r,in::t,w,1);
		}
	}
	s+=n8;
	dij();
	for(int i=1,p=n8;i<=n;i++)
		cout<<(dis[i+p]<1e18?dis[i+p]:-1)<<" \n"[i==n];
}

P6348 [PA2011]Journeys

這道題就是讓我們區間對區間進行連邊,然後求最短路

我們可以建兩個虛點,然後這兩個虛點之間的邊的權值為 \(1\),其他的邊均為 \(0\)

第一個區間連到第一個點上,第二個區間連到第二個點上

因為是雙向邊,我們連兩次單項邊即可

又因為這道題只有 \(0\)\(1\) 兩個權值,我們可以不用最短路,而直接用01BFS去求

【程式碼實現】

#include<iostream>
#include<cstring>
#include<queue>
#define N 10000006
using namespace std;
int n,m,p,fl,fr,tl,tr,n2,n4,n8,dis[N],head[N],num,nont[N],cnt;
struct Edge{int na,np,w;} e[N];
struct segtree{int l,r;} t[N];
deque<int>que;
inline void add(int f,int t,int w=0){
	e[++num].na=head[f];
	e[num].np=t;e[num].w=w;
	head[f]=num;
}
inline void dbadd(int f,int t) {add(f,t);add(t,f);}
inline void build(int p,int l,int r){
	t[p].l=l;t[p].r=r;
	if(l==r){
		nont[l]=p;
		dbadd(p,p+n4);
		return;
	}
	add(p,p<<1),add(p,p<<1|1);
	add((p<<1)+n4,p+n4),add((p<<1|1)+n4,p+n4);
	int mid=(l+r)>>1;
	build(p<<1,l,mid);build(p<<1|1,mid+1,r);
}
inline void sadd(int p,int l,int r,int wp,bool s){
	if(t[p].l==l&&t[p].r==r){
		if(!s) add(wp,p);
		else add(p+n4,wp);
		return;
	}
	int mid=(t[p].l+t[p].r)>>1;
	if(l<=mid) sadd(p<<1,l,r<mid?r:mid,wp,s);
	if(r>mid) sadd(p<<1|1,mid+1<l?l:mid+1,r,wp,s);
}
inline void dbsadd(int l1,int r1,int l2,int r2){
	int p1=++cnt,p2=++cnt;
	add(p2,p1,1);
	sadd(1,l2,r2,p1,0);
	sadd(1,l1,r1,p2,1);
	p1=++cnt,p2=++cnt;
	add(p2,p1,1);
	sadd(1,l2,r2,p2,1);
	sadd(1,l1,r1,p1,0);
	
}
inline void dij(){
	memset(dis,0x3f,sizeof(dis));
	dis[p]=0;que.push_front(p);
	while(que.size()){
		int bf=que.front();que.pop_front();
		for(int i=head[bf];i;i=e[i].na){
			if(dis[e[i].np]>dis[bf]+e[i].w){
				dis[e[i].np]=dis[bf]+e[i].w;
				if(!e[i].w) que.push_front(e[i].np);
				else que.push_back(e[i].np);
			}
		}
	}
}
signed main(){
	cin>>n>>m>>p;
	n2=n<<1;n4=n<<2;n8=n<<3;cnt=n8;
	build(1,1,n);
	for(int i=1;i<=m;i++){
		cin>>fl>>fr>>tl>>tr;
		dbsadd(fl,fr,tl,tr);
	}
	p=nont[p]+n4;
	dij();
	for(int i=1;i<=n;i++) cout<<dis[nont[i]]<<"\n";
}