线段树优化建图 + 迪杰斯特拉找单源最短路

线段树优化建图通常用于处理:

  1. 给出一个点的区间, 都与某个点有单向边.
  2. 给出一个点, 于一个连续区间都有单向边.

注意到, 如果暴力建图, 会达到 n 2 n^2 n2 个边, 显然无法接受.

考虑用到线段树来维护区间, 那么我们就要维护两个东西.

对于线段树的一个节点 node {l, r}:

  1. 哪个节点到 l 到 r 节点都有单向边…
  2. 节点 l 到 r 到哪个节点有单向边.

先看 1, 我们可以用一个点连到线段树的节点 node {l, r} 来表示这个点到 {l, r} 各有一条单向边(橙色). 此时显然: 大区间向被包含的小区间可以有权值为 0 的单向边(绿色). 因为一个点可以到上面的大区间, 一定可以到达被包含的小区间的任意一个点.

注意到, 由有向边组成的路径能到达第 i 个叶子节点, 也就等价于能到达节点 i. 于是, 叶子节点就是未建树前的实际节点.

请添加图片描述
对于 2, 一切同理, 有树:

请添加图片描述
显然, 这两个树是不共存的, 不能公用一个树. 就要用一定形式把这两个树连到一起. 于是…

按照如下建边:

  1. 双向连接两个树的叶子节点, 边权为 0 (二者等价).

  2. 单向连接树边, 边权为 0 :

    1. 树 1 表示一个点向一个区间有单向边, 那这个点向更小的被包含的区间一定有边.
    2. 树 2 表示一个区间向一个点有单向边, 那被包含的更小区间一定也向这个点有单向边.
  3. 有一个点到一个区间的单向边:

    从树 2 的对应叶子向到树 1 的对应区间连边 ( 橙色 ).

  4. 有一个区间到一个点的单向边:

    从树 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;
}

图片来自 博客, 懒癌, 懒着画图, 而且他讲的也很不错.

  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值