线段树优化建图

适用范围

当一张图边数较多且建边的对象是区间时,可以使用线段树优化建图。

例题

CF786B Legacy

首先将方案转化为在图上建边,将方案的价值转化为边权。则问题变为有 \(q\) 次操作,每次操作可以从点向区间的点连边,或从区间向点连边,或点向点连边,求 \(s\) 点到所有点的最短路。

发现总共有 \(q \times n\) 条边,直接建边肯定会超时,但是边的对象是区间,所以想到线段树。

将区间放在线段树上最多有 \(\log n\) 个节点,那么总共就只剩下 \(q \times \log n\) 条边,可以接受。

该如何建图呢,下图是一个示例,点 \(7\) 连向区间 \(1\)\(3\)

但是区间本质由点组成,所以点 \(7\) 是需要连到节点上的,那就让线段树向下连。

这样我们就完成了从点连向区间了,那该如何从区间连向点呢?我们想到可以依然用这棵线段树,将其从下向上连边,然后连向一个节点,就像这样(只画了 \(1\)\(4\)):

但是此时由于红边与绿边的边权都是 \(0\),则任何点都能通过红边与绿边用 \(0\) 代价到达任意地方,于是想到可以建立两棵线段树,一个从上向下连边,代表从点连向区间;一个从下向上连边,代表从区间连向点。下图是 \(8\) 号点连向 \(1\)\(6\) 的区间。

由于两棵线段树的叶子节点本质相同,所以两个叶子节点中间连一条边权为 \(0\) 的无向边。

然而我们还需要新建一个数组来表示一个节点,然后将其连向线段树,太不优美了。所以可以将第二棵线段树上的叶子节点连向第一棵线段树,表示点连向区间;将第二棵线段树连向第一棵线段树上的点,代表区间连向点。这样就成功建完图了。

建完图之后就简单了,跑一遍 dijkstra 就可以了。

ACcode

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define INT_MAX (int)(1e18)
#define mid (l+r>>1)

const int N=1e5+10;

int n,Q,st,idx,sum;
int siz[N],dis[N<<5],vis[N<<5];
int head[N<<5],nxt[N<<5],ver[N<<5],val[N<<5];

//第一棵线段树从上到下,别连接其相当于别连接一个区间 
//第二棵线段树从下到上,其连接别相当于一个区间连接别 

inline int read(){
	int t=0,f=1;
	register char c=getchar();
	while(c<'0'||c>'9') f=(c=='-')?(-1):(f),c=getchar();
	while(c>='0'&&c<='9') t=(t<<3)+(t<<1)+(c^48),c=getchar();
	return t*f;
}

void add(int u,int v,int w){
	nxt[++idx]=head[u];
	head[u]=idx;
	ver[idx]=v;
	val[idx]=w;
}

void init(int bian,int l,int r){
	sum=max(sum,bian);
	if(l==r) return;
	init(bian<<1,l,mid);
	init(bian<<1|1,mid+1,r);
}

void build(int bian,int l,int r){
	if(l==r){add(bian,bian+sum,0),add(bian+sum,bian,0);return;}
	add(bian,bian<<1,0),add(bian,bian<<1|1,0);
	add((bian<<1)+sum,bian+sum,0),add((bian<<1|1)+sum,bian+sum,0);
	build(bian<<1,l,mid);build(bian<<1|1,mid+1,r);
}

int find(int bian,int l,int r,int x){
	if(l==r) return bian;
	if(x<=mid) return find(bian<<1,l,mid,x);
	else return find(bian<<1|1,mid+1,r,x);
}

void ins(int bian,int l,int r,int L,int R,int x,int y,bool flag){
	if(L<=l&&R>=r){
		if(!flag) add(x,bian,y);
		else add(bian+sum,x,y);
		return;
	}
	if(L<=mid) ins(bian<<1,l,mid,L,R,x,y,flag);
	if(R>mid) ins(bian<<1|1,mid+1,r,L,R,x,y,flag);
}

priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > q; 

void dij(){
	for(int i=1;i<=sum*2;i++) dis[i]=INT_MAX;
	dis[st]=0,q.push({dis[st],st});
	while(!q.empty()){
		pair<int,int> x=q.top();q.pop();
		vis[x.second]=true;
		for(int i=head[x.second];i;i=nxt[i]){
			int dao=ver[i];
			if(dis[dao]>x.first+val[i]){
				dis[dao]=x.first+val[i];
				if(vis[dao]) continue;
				q.push({dis[dao],dao});
			}
		}
	}
}

void Get_ans(int bian,int l,int r){
	if(l==r){
		if(dis[bian]==INT_MAX) cout<<-1<<" ";
		else cout<<dis[bian]<<" ";
		return;
	}
	Get_ans(bian<<1,l,mid);
	Get_ans(bian<<1|1,mid+1,r);
}

signed main(){
	n=read(),Q=read(),st=read();
	init(1,1,n);build(1,1,n);
	for(int i=1;i<=Q;i++){
		int op=read();
		if(op==1){
			int u=read(),v=read(),w=read();
			int fu=find(1,1,n,u),fv=find(1,1,n,v);
			add(fu,fv,w); 
		}else{
			int u=read(),l=read(),r=read(),w=read();
			ins(1,1,n,l,r,find(1,1,n,u),w,op-2);
		}
	}st=find(1,1,n,st);
	dij();Get_ans(1,1,n);
	return 0;
}
posted @ 2025-03-29 10:50  ask_silently  阅读(107)  评论(0)    收藏  举报