线段树优化建图通常用于处理:
- 给出一个点的区间, 都与某个点有单向边.
- 给出一个点, 于一个连续区间都有单向边.
注意到, 如果暴力建图, 会达到 n 2 n^2 n2 个边, 显然无法接受.
考虑用到线段树来维护区间, 那么我们就要维护两个东西.
对于线段树的一个节点 node {l, r}:
- 哪个节点到 l 到 r 节点都有单向边…
- 节点 l 到 r 到哪个节点有单向边.
先看 1, 我们可以用一个点连到线段树的节点 node {l, r} 来表示这个点到 {l, r} 各有一条单向边(橙色). 此时显然: 大区间向被包含的小区间可以有权值为 0 的单向边(绿色). 因为一个点可以到上面的大区间, 一定可以到达被包含的小区间的任意一个点.
注意到, 由有向边组成的路径能到达第 i 个叶子节点, 也就等价于能到达节点 i. 于是, 叶子节点就是未建树前的实际节点.
对于 2, 一切同理, 有树:
显然, 这两个树是不共存的, 不能公用一个树. 就要用一定形式把这两个树连到一起. 于是…
按照如下建边:
-
双向连接两个树的叶子节点, 边权为 0 (二者等价).
-
单向连接树边, 边权为 0 :
- 树 1 表示一个点向一个区间有单向边, 那这个点向更小的被包含的区间一定有边.
- 树 2 表示一个区间向一个点有单向边, 那被包含的更小区间一定也向这个点有单向边.
-
有一个点到一个区间的单向边:
从树 2 的对应叶子向到树 1 的对应区间连边 ( 橙色 ).
-
有一个区间到一个点的单向边:
从树 2 的对应区间向树 1 的对应叶子连边 ( 粉色 ).
这样就可以把暴力的每次要建 O(r-l) 条边, 优化到建 O(log r-l) 条边
建完图后直接把第一颗树的叶子节点当作原先的节点正常使用即可.
如果要实现类似线段树的 lazy 优化也是可以的. 即到达了这个线段树节点, 就是可以到这个节点所代表的区间内的所有点.
以题目 786B - Legacy 为例, 要求线段树建完图后的单源最短路, 直接看代码:
#include<bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#include "D:\Users\TauLee\OneDrive\Mine\c++\debug.h"
#else
#define debug(...) 42;
#define endl '\n'
#endif
#define int long long
//----线段树优化建图----//
#define pii pair<int,int>
const int Maxn = 1e5 + 10;
struct node {
int l, r;
} tr[Maxn << 3];
const int D = Maxn << 2; // tr 偏移 D 作为第二棵树.
int leaf[Maxn]; // 每个节点对应的叶子节点;
vector<pii>eg[Maxn << 3]; // 树上的虚拟边
void build(int rt/*=1*/, int l, int r) {
tr[rt].l = l;
tr[rt].r = r;
if (l == r) {
leaf[l] = rt;
return ;
}
eg[rt].emplace_back(pii(rt << 1, 0));
eg[rt].emplace_back(pii(rt << 1 | 1, 0)); // 建立第一棵树的边
eg[(rt << 1) + D].emplace_back(pii(rt + D, 0));
eg[(rt << 1 | 1) + D].emplace_back(pii(rt + D, 0)); // 建立第二棵树的边
int mid = (r + l) >> 1;
build(rt << 1, l, mid);
build(rt << 1 | 1, mid + 1, r);
}
void init(int &n) { // 初始化
build(1, 1, n); // 建立树边
for (int i = 1; i <= n; ++i) { // 建立两棵树叶子之间的边
eg[leaf[i] + D].emplace_back(pii(leaf[i], 0));
eg[leaf[i]].emplace_back(pii(leaf[i] + D, 0));
}
}
void addeg(int u, int v, int w) {
eg[leaf[u]].emplace_back(pii(leaf[v], w));
}
void addeg(int rt/*=1*/, int v, int l, int r, int w, int opt) {
// opt==0, 点连区间
// opt==1, 区间连点
if (l > tr[rt].r || r < tr[rt].l)return;
if (l <= tr[rt].l && tr[rt].r <= r) {
if (opt)
eg[rt + D].emplace_back(pii(leaf[v], w));
else
eg[leaf[v] + D].emplace_back(pii(rt, w));
return;
}
addeg(rt << 1, v, l, r, w, opt);
addeg(rt << 1 | 1, v, l, r, w, opt);
}
//----线段树优化建图----//
int n, q, s, opt, v, u, w, l, r, dist[Maxn << 3];
signed main()
{
ios::sync_with_stdio(false); cin.tie(nullptr); cout.tie(nullptr);
cin >> n >> q >> s;
init(n);
for (int i = 1; i <= q; ++i) {
cin >> opt;
if (opt == 1) {
cin >> v >> u >> w;
addeg(v, u, w);
}
else if (opt == 2) {
cin >> v >> l >> r >> w;
addeg(1, v, l, r, w, 0);
}
else {
cin >> v >> l >> r >> w;
addeg(1, v, l, r, w, 1);
}
}
priority_queue<pii, vector<pii>, greater<pii> >q;
debug(s)
for (int i = 1; i < (Maxn << 3); ++i) {
dist[i] = LLONG_MAX;
}
dist[leaf[s]] = 0;
q.emplace(pii(0, leaf[s])); // 权值, 终点反过来
while (!q.empty()) {
auto [w, s] = q.top();
q.pop();
if (dist[s] < w)continue;
for (auto [t, ww] : eg[s]) {
if (w + ww < dist[t]) {
dist[t] = w + ww;
q.emplace(pii(dist[t], t));
}
}
}
for (int i = 1; i <= n; ++i) {
if (dist[leaf[i]] == LLONG_MAX)cout << -1 << " ";
else cout << dist[leaf[i]] << " ";
}
cout << endl;
}
图片来自 博客, 懒癌, 懒着画图, 而且他讲的也很不错.