线段树优化建图

线段树优化建图

解决的情景:建图时,一个点 \(x\) 要对区间 \([L,R]\) 中的点连边,直接暴力连边时间会爆炸。

线段树可以把一个区间拆分成 \(\log N\) 个线段树上的区间,连边的时候对这些区间连边即可。

但是还需要解决:当区间 \([L,R]\) 向点 \(x\) 连边之后,区间 \([L,R]\) 之内的任何数都可以到达点 \(x\)

需要建两颗线段树,出树和入树。

出树中的边都是由儿子连向父亲,入树中的边由父亲连向儿子。

连边

区间连点

将出树上的若干个组成区间 \([L,R]\) 的点连向入树上的叶子结点 \(x\)

点连区间

将出树上的叶子结点 \(x\) 连向出树上组成区间 \([L,R]\) 的点。

区间连区间

若将出树上的 \(\log N\) 个区间连向入树上的 \(\log N\) 个区间,时间复杂度是 \(O(\log^2 N)\)

可以将出树上 \(\log N\) 个点连向一个新节点,将新节点分别连向入树上的 \(\log N\) 个区间。

无向边

上面是有向边的连法。

无向图需要建双向边,在区间连区间时,两条边不能同用一个虚点,不然会有自环。

新建一个虚点对用了普通图中建一条单向边,所以双向边需要建两个虚点。

应用场景

一来就说了。

一个图论题/一个需要图论建模的题目,需要点连区间/区间连点/区间连区间时可以考虑线段树优化建图。

例题

https://codeforces.com/problemset/problem/786/B

#include<bits/stdc++.h>
#define int long long
#define lc f[p].l
#define rc f[p].r
#define endl putchar('\n')
#define psp putchar(' ')
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int N=5e5+5;
int read(){
	int x=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
	return x*f;
}
void print(int x){
	if(x<0)putchar('-'),x=-x;
	if(x<10){putchar(x+'0');return;}
	print(x/10);
	putchar(x%10+'0');
}
void putstr(string s){
	for(int i=0;i<s.size();i++)putchar(s[i]);
}
int lowbit(int x){
	return x&-x;
}
int n,m,k;
int T;
int s;
struct val{
	int to,dis;
	friend bool operator<(val a,val b){
		return a.dis>b.dis;
	}
};
vector<val>a[N];
int to_down[N];
int to_up[N];
struct node{
	int l,r;
}f[N];
int idx;
void build(int &pa,int &pb,int l,int r){
	if(!pa)pa=++idx;
	if(!pb)pb=++idx;
	if(l==r){
		to_up[l]=pa;
		to_down[l]=pb;
		a[pa].push_back({pb,0});
		a[pb].push_back({pa,0});
		return;
	}
	int mid=l+r>>1;
	build(f[pa].l,f[pb].l,l,mid);
	build(f[pa].r,f[pb].r,mid+1,r);
	a[f[pa].l].push_back({pa,0});
	a[f[pa].r].push_back({pa,0});
	a[pb].push_back({f[pb].l,0});
	a[pb].push_back({f[pb].r,0});
}
int rt_up,rt_down;
void update_down(int p,int L,int R,int l,int r,int u,int w){
	if(l<=L&&R<=r){
		a[to_up[u]].push_back({p,w});
		return;
	}
	int mid=L+R>>1;
	if(l<=mid)update_down(lc,L,mid,l,r,u,w);
	if(r>mid)update_down(rc,mid+1,R,l,r,u,w);
}
void update_up(int p,int L,int R,int l,int r,int u,int w){
	if(l<=L&&R<=r){
		a[p].push_back({to_down[u],w});
		return;
	}
	int mid=L+R>>1;
	if(l<=mid)update_up(lc,L,mid,l,r,u,w);
	if(r>mid)update_up(rc,mid+1,R,l,r,u,w);
}
int dis[N];
int vis[N];
void dij(int rt){
	priority_queue<val>q;
	for(int i=1;i<=idx;i++)dis[i]=1e18;
	dis[rt]=0;
	q.push(val{rt,0});
	while(!q.empty()){
		int x=q.top().to;
		q.pop();
		if(vis[x])continue;
		vis[x]=1;
		for(int i=0;i<a[x].size();i++){
			int y=a[x][i].to;
			int w=a[x][i].dis;
			if(dis[x]+w<dis[y]){
				dis[y]=dis[x]+w;
				q.push(val{y,dis[y]});
			}
		}
	}
}
signed main(){
	//ios::sync_with_stdio(0);
	n=read(),m=read(),s=read();
	build(rt_up,rt_down,1,n);
	for(int i=1;i<=m;i++){
		int op=read();
		if(op==1){
			int u=read(),v=read(),w=read();
			a[to_up[u]].push_back({to_down[v],w});
		}
		else if(op==2){
			int u=read(),l=read(),r=read(),w=read();
			update_down(rt_down,1,n,l,r,u,w);
		}
		else{
			int u=read(),l=read(),r=read(),w=read();
			update_up(rt_up,1,n,l,r,u,w);
		}
	}
	dij(to_up[s]);
	for(int i=1;i<=n;i++){
		int num=min(dis[to_up[i]],dis[to_down[i]]);
		if(num>=1e18)print(-1);
		else print(num);
		psp;
	}
}

倍增优化

倍增求 LCA 的时候,每次向上跳 \(2^k\) 个。

可以考虑新建点 \((x,k)\) 表示 \(x\to f_{k,x}\) 中的路径,类似于线段树优化建图。

对于 \(x->y\) 可以找到一个最大的 \(k\),使 \(2^k\le len\),可以将区间拆分为两个长度为 \(2^k\) 的区间,连边即可。

posted on 2026-07-17 21:30  fish2012  阅读(7)  评论(0)    收藏  举报