線段樹最佳化建圖學習筆記

harmis_yz發表於2024-04-18

關於線段樹最佳化建圖

對於存在一些單點連向區間或區間連向單點的邊,且直接暴力連邊會爆炸的題目,就可以考慮使用線段樹最佳化建圖。

邊數量的規模將會是 \(n \log n+a\)

例題

題目連結

\(s\)\(t\) 的最短路就是模板。如果暴力建邊,最壞情況下需要建的邊在 \(n^2\) 級別,顯然是不可能的。

考慮線段樹最佳化建圖。對於第 \(1\) 種建邊方式,只能是直接將 \(u,v\) 連邊。而對於 \(2,3\) 兩種建邊方式,開兩棵線段樹分別維護單點連向區間的邊與區間連向單點的邊。第 \(1\) 棵線段樹的邊均為父親連向兒子,第 \(2\) 棵線段樹的邊均為兒子連向父親。

對於第 \(2\) 種建邊方式,若線段樹上點 \(u\) 包含的區間 \([u_l,u_r]\) 均在區間 \([l,r]\) 裡,則 \([u_l,u_r]\) 中任意一個點都會與 \(v\) 連邊。則將 \(v\) 在第 \(2\) 棵線段樹上的點與 \(u\) 連邊。第 \(3\) 種 和第 \(2\) 種情況類似。

兩棵線段樹中對應的葉子節點表示的節點是一樣的,所以也要將它們連邊。

可以看一下 OI Wiki 的圖。

程式碼如:

const int M=5e5+1;
il void add(int a,int b,int c){
	ne[++idx]=h[a],e[idx]=b,w[idx]=c,h[a]=idx;
}
il void build(int now,int l,int r){
	tr[now].l=l,tr[now].r=r;
	if(l==r) return id[l]=now,void(0);
	int mid=l+r>>1;
	add(now,now<<1,0),add(now,now<<1|1,0);//第1棵線段樹
	add((now<<1)+M,now+M,0),add((now<<1|1)+M,now+M,0);//第2棵線段樹 
	build(now<<1,l,mid),build(now<<1|1,mid+1,r);
	return ;
}
il void Add(int now,int l,int r,int v,int w,bool flag){
	if(tr[now].l>=l&&tr[now].r<=r){
		if(!flag) add(id[v]+M,now,w);//點連向區間
		else add(now+M,id[v],w);//區間連向點
		return ; 
	}
	int mid=tr[now].l+tr[now].r>>1;
	if(l<=mid) Add(now<<1,l,r,v,w,flag);
	if(mid<r) Add(now<<1|1,l,r,v,w,flag);
	return ;
}
il void init(){
	for(re int i=1;i<=n;++i)
		add(id[i],id[i]+M,0),
		add(id[i]+M,id[i],0);
	return ; 
}

然後這道題就是建完圖之後跑一個 dij 求一個單源最短路。

注:起點將是第 \(2\) 棵線段樹的葉子節點。

參考程式碼:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define re register
#define il inline
#define pii pair<int,int>
#define x first
#define y second
#define gc getchar()
#define rd read()
#define debug() puts("------------")

namespace yzqwq{
	il int read(){
		int x=0,f=1;char ch=gc;
		while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=gc;}
		while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=gc;
		return x*f;
	}
	il int qmi(int a,int b,int p){
		int ans=1;
		while(b){
			if(b&1) ans=ans*a%p;
			a=a*a%p,b>>=1;
		}
		return ans;
	}
	il auto max(auto a,auto b){return (a>b?a:b);}
	il auto min(auto a,auto b){return (a<b?a:b);}
	il int gcd(int a,int b){
		if(!b) return a;
		return gcd(b,a%b);
	}
	il int lcm(int a,int b){
		return a/gcd(a,b)*b;
	}
	il void exgcd(int a,int b,int &x,int &y){
		if(!b) return x=1,y=0,void(0);
		exgcd(b,a%b,x,y);
		int t=x;
		x=y,y=t-a/b*x;
		return ;
	}
	mt19937 rnd(time(0));
}
using namespace yzqwq;

const int N=3e6+10,M=5e5+1;
int n,m,s,id[N];
int ne[N],e[N],w[N],h[N],idx;
struct Tree{
	int l,r;
}tr[N];
int vis[N],dis[N];

il void add(int a,int b,int c){
	ne[++idx]=h[a],e[idx]=b,w[idx]=c,h[a]=idx;
}
il void build(int now,int l,int r){
	tr[now].l=l,tr[now].r=r;
	if(l==r) return id[l]=now,void(0);
	int mid=l+r>>1;
	add(now,now<<1,0),add(now,now<<1|1,0);//第1棵線段樹
	add((now<<1)+M,now+M,0),add((now<<1|1)+M,now+M,0);//第2棵線段樹 
	build(now<<1,l,mid),build(now<<1|1,mid+1,r);
	return ;
}
il void Add(int now,int l,int r,int v,int w,bool flag){
	if(tr[now].l>=l&&tr[now].r<=r){
		if(!flag) add(id[v]+M,now,w);//點連向區間
		else add(now+M,id[v],w);//區間連向點
		return ; 
	}
	int mid=tr[now].l+tr[now].r>>1;
	if(l<=mid) Add(now<<1,l,r,v,w,flag);
	if(mid<r) Add(now<<1|1,l,r,v,w,flag);
	return ;
}
il void init(){
	for(re int i=1;i<=n;++i)
		add(id[i],id[i]+M,0),
		add(id[i]+M,id[i],0);
	return ; 
}

il void dij(){
	priority_queue<pii,vector<pii>,greater<pii>> qu;
	memset(dis,0x3f,sizeof(dis)),
	memset(vis,0,sizeof(vis));
	dis[id[s]+M]=0,qu.push({0,id[s]+M});
	while(!qu.empty()){
		pii now=qu.top();qu.pop();
		if(vis[now.y]) continue;
		vis[now.y]=1;
		for(re int i=h[now.y];i;i=ne[i]){
			int j=e[i];
			if(dis[j]>dis[now.y]+w[i])
				dis[j]=dis[now.y]+w[i],
				qu.push({dis[j],j});
		}
	}
	return ;
}

il void solve(){
	n=rd,m=rd,s=rd;
	build(1,1,n),init();
	for(re int i=1;i<=m;++i){
		int op=rd,v=rd,u,l,r,w;
		if(op==1){
			u=rd,w=rd;
			add(id[v]+M,id[u],w);
		}
		else if(op==2){
			l=rd,r=rd,w=rd;
			Add(1,l,r,v,w,0);
		}
		else{
			l=rd,r=rd,w=rd;
			Add(1,l,r,v,w,1);
		}
	}
	dij();
	for(re int i=1;i<=n;++i) printf("%lld ",dis[id[i]]>=1e18?-1:dis[id[i]]);
	return ;
}

signed main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int t=1;while(t--)
	solve();
	return 0;
}

相關文章