线段树优化建图
线段树优化建图:高效处理大规模区间连边问题
在面对图论问题中需要将单点连接到整个区间,或将整个区间连接到单点的场景时,线段树优化建图通过引入辅助的线段树结构,将建边复杂度从 O(n) 降至 O(log n),极大提升了算法效率。
一、问题背景:大规模区间连边的困境
在传统图论问题中,若需实现以下操作:
- 单点向区间连边:
u → [L, R](u 到区间内所有点连边) - 区间向单点连边:
[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;
}

浙公网安备 33010602011771号