线段树优化建图

线段树优化建图:高效处理大规模区间连边问题

在面对图论问题中需要将单点连接到整个区间,或将整个区间连接到单点的场景时,线段树优化建图通过引入辅助的线段树结构,将建边复杂度从 O(n) 降至 O(log n),极大提升了算法效率。

一、问题背景:大规模区间连边的困境

在传统图论问题中,若需实现以下操作:

  1. 单点向区间连边u → [L, R](u 到区间内所有点连边)
  2. 区间向单点连边[L, R] → v(区间内所有点到 v 连边)

常规做法是遍历区间内每个点进行连接。每次操作时间复杂度为 O(区间长度),最坏情况下达到 O(n)。当操作次数较多时,总复杂度可能达到 O(n²),难以承受。

线段树优化建图的核心思想:构建两棵辅助线段树——入树出树,将原图中的点视为叶子节点。区间连边操作转化为线段树上 O(log n) 个节点之间的连接,大幅减少边数。

二、核心结构:入树与出树

1. 入树

  • 方向:边由子节点指向父节点(自底向上)
  • 功能:处理进入某个区间的边(区间作为终点)
  • 逻辑:信息/影响从叶子汇聚到根,代表整个区间接受输入

2. 出树

  • 方向:边由父节点指向子节点(自顶向下)
  • 功能:处理从某个区间发出的边(区间作为起点)
  • 逻辑:信息/影响从根分发到叶子,代表整个区间产生输出

3. 关键连接

  • 两棵树的叶子节点对应原始图的节点(同一物理点)
  • 连接两棵树叶子节点:T_out 的叶子 → T_in 的叶子(权值为0)


线段树优化建图结构示意图(来源:OI Wiki)

三、操作实现与原理

操作类型 实现步骤
单点→区间 (u→[L,R]) 1. 在出树中定位区间对应的 O(log n) 个节点
2. 连接 u 的出树叶子到这些节点
区间→单点 ([L,R]→v) 1. 在入树中定位区间对应的 O(log n) 个节点
2. 连接这些节点到 v 的入树叶子
点对点 (u→v) 直接连接 u 的出树叶子 → v 的入树叶子

四、复杂度分析

操作类型 传统方法复杂度 线段树优化复杂度
初始化建树 - O(n)
单点 → 区间 O(n) O(log n)
区间 → 单点 O(n) O(log n)
点 → 点 O(1) O(1)
总边数 O(n²) O(n log n)
空间复杂度 O(n) O(n log n)

例题可以看CF786B

#include<bits/stdc++.h>
#define int long long
#define ls(p) (p<<1)
#define rs(p) ((p<<1)|1)
using namespace std;
const int N=3e6+10,SPAN=5e5;
int n,m,s,orr;
int a[N];
struct node_edge_vector{int v,w;};
vector<node_edge_vector>G[N];
 
 int dis[N];
 bool vis[N];
 priority_queue<pair<int,int> >q;
 //
void build(int p,int l,int r){
	if(l==r){
		a[l]=p;
		return;
	}
	int mid=(l+r)>>1;
	G[p].push_back({ls(p),0});
	G[p].push_back({rs(p),0});
	
	G[ls(p)+SPAN].push_back({p+SPAN,0});
	G[rs(p)+SPAN].push_back({p+SPAN,0});
	
	build(ls(p),l,mid),build(rs(p),mid+1,r);
}
void update(int p,int l,int r,int L,int R,int v,int w){
	if(L<=l&&r<=R){
		if(orr==2){
			G[v+SPAN].push_back({p,w});

		}
		else{
			G[p+SPAN].push_back({v,w});

		}
		return;
	}
	int mid=(l+r)>>1;
	if(L<=mid)update(ls(p),l,mid,L,R,v,w);
	if(R>mid)update(rs(p),mid+1,r,L,R,v,w);
}
void dij(int start){
	for(int i=0;i<N;i++)dis[i]=1e18;
	dis[start]=0;
	q.push({0,start});

	while(!q.empty()){
		int u=q.top().second;
		q.pop();
		if(vis[u])continue;
		vis[u]=1;
		for(int i=0;i<G[u].size();i++){
			int v=G[u][i].v,w=G[u][i].w;
			if(dis[v]>dis[u]+w){
				dis[v]=dis[u]+w;
				q.push({-dis[v],v});
			}
		}
	}
}
signed main(){
	cin>>n>>m>>s;
	build(1,1,n);
	for(int i=1;i<=n;i++){
		G[a[i]].push_back({a[i]+SPAN,0});
		G[a[i]+SPAN].push_back({a[i],0});
	}
	for(int i=1;i<=m;i++){
		int u,v,w,l,r;
		cin>>orr;
		if(orr==1){
			cin>>u>>v>>w;
			G[a[u]+SPAN].push_back({a[v],w});

		}
		else{
			cin>>u>>l>>r>>w;
			update(1,1,n,l,r,a[u],w);
		}
	}
	dij(a[s]+SPAN);

	for(int i=1;i<=n;i++){
		if(dis[a[i]]<1e18)cout<<dis[a[i]]<<' ';
		else cout<<-1<<' ';
	}
	cout<<'\n';
	return 0;
}

炸弹

先处理单个炸弹的范围,在向范围内的炸弹用线段树优化建图的方式连边,最后在\(tarjan\)缩点,重新\(dfs\)更新一次炸弹的范围

#include<bits/stdc++.h>
#define int long long
#define fore(i, a, b) for( int i = (a); i <= (b); ++ i)
#define repe(i, a, b) for( int i = (a); i >= (b); -- i)
using namespace std;
const int N = 500010;
const int M = 2000010;
const int mod = 1e9 + 7;
struct Tree
{
	int l, r;
}a[M];
vector<int>T[M];// ????¨º¡Â?¨²¦Ì?
vector<int>G[M];// tarjan??¦Ì?o¨®
int x[N], r[N];// ?¡§¦Ì¡¥D??¡é
int col[M], low[M], dfn[M], st[M]; // ??¦Ì?
int id[M]; // ?¨²¦Ì??¨²????¨º¡Â¨¦???¨®|¦Ì?????
bool vis[M]; // ¡Á?o¨®dfs o¨ª??¦Ì?¦Ì????¨²¡À¨º??
int n, ans, ND, tim, top, scc; 
int bomb_l[M], bomb_r[M]; // ?¡§¦Ì¡¥?¡§¦Ì?¦Ì?¡¤??¡ì??¨®|¦Ì??¡§¦Ì¡¥¡Á¨®¨®¨°¡À¨¤o?
inline int ls(int p)
{
	return (p << 1);
}
inline int rs(int p)
{
	return (p << 1) | 1;
}
void build(int p,int l,int r)
{
	a[p] = {l, r};
	ND = max(ND, p);
	if(l == r)
	{
		id[l] = p;
		// ¨ª?¨¦?¦Ì?l?¨²¦Ì??¨²¨º¡Â¨¦?¦Ì?p
		return;
	}
	int mid = (l + r) >> 1;
	build(ls(p), l, mid);
	build(rs(p), mid + 1, r);
	// ¨¢??¨®????¨º¡Â
	T[p].push_back(ls(p));
	T[p].push_back(rs(p));
}
void update(int p,int l,int r,int L,int R,int k)
{
	if(L <= l && r <= R)
	{
		if(k == p)return;
		T[k].push_back(p);
		return;
	}
	int mid = (l + r) >> 1;
	if(L <= mid) update(ls(p), l, mid, L, R, k);
	if(R >= mid + 1) update(rs(p), mid + 1, r, L, R, k);
	
}

void tarjan(int u)
{
	dfn[u] = low[u] = ++ tim;
	st[++ top] = u;
	vis[u] = 1;
	for(int v : T[u])
	{
		if(dfn[v] == 0)
		{
			tarjan(v);
			low[u] = min(low[u], low[v]);
		}
		else if(vis[v])
		{
			low[u] = min(low[u], dfn[v]);
		}
	}
	if(low[u] != dfn[u]) return;
	++ scc;
	do
	{
		int v = st[top];
		col[v] = scc;
		vis[v] = 0;
		bomb_l[scc] = min(bomb_l[scc], a[v].l);
		bomb_r[scc] = max(bomb_r[scc], a[v].r);
		top --;
	}while(st[top + 1] != u);
	
}
void dfs(int u)
{
	vis[u] = 1;
	for(int v : G[u])
	{
		if(vis[v])
		{
			bomb_l[u] = min(bomb_l[u] , bomb_l[v]);
			bomb_r[u] = max(bomb_r[u] , bomb_r[v]);
		}
		else
		{
			dfs(v);
			bomb_l[u] = min(bomb_l[u] , bomb_l[v]);
			bomb_r[u] = max(bomb_r[u] , bomb_r[v]);
		}
	}
}
int query(int x)
{
	int u = col[id[x]];
	return bomb_r[u] - bomb_l[u] + 1;
}
signed main()
{
	ios::sync_with_stdio(false);
//	freopen(".in", "r", stdin);
//	freopen(".out", "w", stdout);
	cin >> n;
	fore(i, 1, n)
	{	
		cin >> x[i] >> r[i];
	}
	memset(bomb_l, 0x3f, sizeof(bomb_l));
	x[n + 1] = 0x3f3f3f3f3f3f3f3fll;
	//3?¨º??¡¥o¨ª¨¦¨²¡À?
	build(1, 1, n);
	fore(i, 1, n)
	{
		if(r[i] == 0)continue;
		int BL = lower_bound(x + 1, x + n + 1, x[i] - r[i]) - x;
		int BR = upper_bound(x + 1, x + n + 1, x[i] + r[i]) - x - 1;
		update(1, 1, n, BL, BR, id[i]);
		a[id[i]] = {BL, BR};
	}
	
	tarjan(1); // ??¦Ì?
	fore(i, 1, ND)
	{
		for(int v: T[i])
		{
			if(col[i] != col[v])
			{
				G[col[i]].push_back(col[v]);
			}
		}
	}
	memset(vis, 0, sizeof(vis));
	fore(i, 1, scc)
	{
		sort(G[i].begin(), G[i].end());
		unique(G[i].begin(), G[i].end());
	}
	memset(vis, 0, sizeof(vis));
	fore(i, 1, scc)
	{
		if(!vis[i]) dfs(i);
	}
	fore(i, 1, n)
	{
		ans += (query(i) * i);
		ans %= mod;
	}
	cout << ans << '\n';
	return 0;
}



P7214

#include<bits/stdc++.h>
#define int long long
#define fore(i,a,b) for( int i=(a); i<=(b); ++i)
#define repe(i,a,b) for( int i=(a); i>=(b); --i)
using namespace std;
const int N = 1e5 + 10;
int n, m;
struct node {
	int t, l, r, c;
} a[N];	
priority_queue<pair<int,int> > q;
bool cmp(node A,node B) {
	return A.t < B.t;
}
vector<int>G;
int dis[N];
struct Tree {
	int l, r, mid;
	int vL, vR;
	Tree *ls, *rs;
	Tree(int l,int r):l(l),r(r),vL(1e18),vR(1e18){
		if(l + 1 < r) {
			mid = (l + r) >> 1;
			ls = new Tree(l, mid);
			rs = new Tree(mid, r);
		}
	}
	void update(int p,int L,int R) {
		if(l + 1 == r) {
			vL = L; vR = R;
			return;
		}
		if(p < mid) ls->update(p, L, R);
		else rs->update(p, L, R);
		
		vL = min(rs->vL, ls->vL);
		vR = min(rs->vR, ls->vR);
	}
	void query(int L,int R,int v,int orr) {
		int val;
		if(orr == 1) val = vL;
		else val = vR;
		if(val > v) return;
		if(l + 1 == r) {
			G.push_back(l);
			vL = vR = 1e18;
			return;
		}
		if(L < mid) ls->query(L, R, v, orr);
		if(R > mid) rs->query(L, R, v, orr);
		
		vL = min(rs->vL, ls->vL);
		vR = min(rs->vR, ls->vR);
	}
};
signed main(){
	ios::sync_with_stdio(false);
	cin >> n >> m;
	for(int i = 0;i < m;i ++) {
		cin >> a[i].t >> a[i].l >> a[i].r >> a[i].c;
		a[i].l --;
	}
	sort(a, a + m, cmp);
	Tree* Rt = new Tree(0, m);
	for(int i = 0;i < m;i ++) {
		if(a[i].l == 0) {
			dis[i] = a[i].c;
			q.push({-a[i].c, i});
			Rt->update(i, 1e18, 1e18);
		} else {
			Rt->update(i, a[i].l - a[i].t, a[i].l + a[i].t);
			dis[i] = 1e18;
		}
	}
	
	while(!q.empty()) {
		int u = q.top().second;
		q.pop();
		G.clear();
		Rt->query(0, u, a[u].r - a[u].t, 1);
		Rt->query(u + 1, n, a[u].r + a[u].t, 0);
		for(int v : G) {
			if(dis[u] + a[v].c < dis[v]) {
				dis[v] = dis[u] + a[v].c;
				q.push({-dis[v], v});
			}
		}
	}
	
	int ans = 1e18;
	for(int i = 0; i < m; i ++) {
		if(a[i].r == n) ans = min(ans, dis[i]);
	}
	if(ans == 1e18) cout << -1 << '\n';
	else cout << ans << '\n';
	return 0;
}
posted @ 2025-07-23 15:18  wmq2012  阅读(227)  评论(0)    收藏  举报