题解 P4779 【【模板】单源最短路径(标准版)】

· · 题解

本题解已更新 2018.11.18

是时候来一篇线段树的题解了。

线段树的效率还是挺优秀的,至少我自己测下来比我原来写的优先队列快。

先考虑一下如果要优化dijkstra算法,优先队列需要哪些操作,让线段树来实现它们。

如果我们用线段树来维护dist数组,那么修改操作就是非常简单的单点修改。我们只要用线段树来维护区间dist最小元素是谁(minpos,简称mp)即可。

但是线段树是不支持删除元素的,我们可以把dist[0]设置为恒为inf,如果要删除一个元素只要让它对应的叶节点mp值=0即可。这样的话就会保证查询dist最小元素的时候一定不会查到它;如果查到它就说明“优先队列”已经空了。

这是普通线段树的版本

#include <cctype>
#include <cstdio>
#include <climits>
#include <algorithm>

#define rep(I, A, B) for (int I = (A); I <= (B); ++I)
#define dwn(I, A, B) for (int I = (A); I >= (B); --I)
#define erp(I, X) for (int I = head[X]; I; I = next[I])

template <typename T> inline void read(T& t) {
    int f = 0, c = getchar(); t = 0;
    while (!isdigit(c)) f |= c == '-', c = getchar();
    while (isdigit(c)) t = t * 10 + c - 48, c = getchar();
    if (f) t = -t;
}
template <typename T, typename... Args>
inline void read(T& t, Args&... args) {
    read(t); read(args...); 
}
template <typename T> void write(T x) {
    if (x < 0) x = -x, putchar('-');
    if (x > 9) write(x / 10);
    putchar(x % 10 + 48);
}
template <typename T> void writeln(T x) {
    write(x);
    puts("");
}
template <typename T> inline bool chkMin(T& x, const T& y) { return y < x ? (x = y, true) : false; }
template <typename T> inline bool chkMax(T& x, const T& y) { return x < y ? (x = y, true) : false; }

const int maxn = 1e5 + 207, maxm = 2e5 + 207, inf = INT_MAX;
int v[maxm], w[maxm], head[maxn], next[maxm], tot;
int dist[maxn], mp[maxn << 2];
int n, m, s;

inline void ae(int x, int y, int z) {
    v[++tot] = y; w[tot] = z; next[tot] = head[x]; head[x] = tot;
}
inline int cmp(int a, int b) { return dist[a] < dist[b] ? a : b; }
inline void upd(int x) { mp[x] = cmp(mp[x << 1], mp[x << 1 | 1]); }
void modify(int o, int l, int r, int pos, int val) {
    if (l == r) { mp[o] = val; return; }
    int mid = (l + r) >> 1;
    if (pos <= mid) modify(o << 1, l, mid, pos, val);
    else modify(o << 1 | 1, mid + 1, r, pos, val);
    upd(o);
}
inline void dijkstra(int s) {
    rep(i, 0, n) dist[i] = inf;
    dist[s] = 0; modify(1, 1, n, s, s);
    rep(t, 1, n - 1) {
        int x = mp[1]; modify(1, 1, n, x, 0);
        // 注意chkMin函数中已对dist[v[i]]赋值。
        erp(i, x) if (chkMin(dist[v[i]], dist[x] + w[i]))
            modify(1, 1, n, v[i], v[i]);
    }
}

int main() {
    read(n, m, s);
    rep(i, 1, m) {
        int x, y, z; read(x, y, z); ae(x, y, z);
    }
    dijkstra(s);
    rep(i, 1, n) write(dist[i]), putchar(' ');
    puts("");
    return 0;
}

下面是zkw线段树的版本,比普通线段树快。(长达30行的程序头已略去)

参考了@Kelin大佬的写法,在这里表示感谢和膜拜。

const int maxn = 1e5 + 207, maxm = 2e5 + 207, inf = INT_MAX;
int v[maxm], w[maxm], head[maxn], next[maxm], tot;
int dist[maxn], mp[maxn << 2], M = 1;
int n, m, s;

inline void ae(int x, int y, int z) { v[++tot] = y; w[tot] = z; next[tot] = head[x]; head[x] = tot; }
inline int cmp(int a, int b) { return dist[a] < dist[b] ? a : b; }
inline void build(int n) {
    while (M < n + 2) M <<= 1;
    mp[0] = n + 1;
}
inline void modify(int x, int nv) {
    for (int i = x + M; dist[mp[i]] > nv; i >>= 1)
        mp[i] = x;
    dist[x] = nv;
}
inline void del(int x) {
    for (mp[x += M] = 0, x >>= 1; x; x >>= 1)
        mp[x] = cmp(mp[x << 1], mp[x << 1 | 1]);
}
inline void dijkstra(int s) {
    rep(i, 0, n) dist[i] = inf;
    build(n); modify(s, 0);
    rep(t, 1, n - 1) {
        int x = mp[1]; del(x);
        // 这里没有对dist[v[i]]赋值,赋值操作在modify函数里进行,与上一个代码有所区别
        erp(i, x) if (dist[v[i]] > dist[x] + w[i])
            modify(v[i], dist[x] + w[i]);
    }
}

int main() {
    read(n, m, s);
    rep(i, 1, m) {
        int x, y, z; read(x, y, z); ae(x, y, z);
    }
    dijkstra(s);
    rep(i, 1, n) write(dist[i]), putchar(' ');
    puts("");
    return 0;
}

另附强势压行版本。4行线段树你没有看错

const int maxn = 1e5 + 207, maxm = 2e5 + 207, inf = INT_MAX;
int v[maxm], w[maxm], head[maxn], next[maxm], tot;
int dist[maxn], mp[maxn << 2], M = 1;
int n, m, s;

inline void ae(int x, int y, int z) { v[++tot] = y; w[tot] = z; next[tot] = head[x]; head[x] = tot; }
inline int cmp(int a, int b) { return dist[a] < dist[b] ? a : b; }
inline void build(int n) { while (M < n + 2) M <<= 1; mp[0] = n + 1; }
inline void modify(int x, int nv) { for (int i = x + M; dist[mp[i]] > nv; i >>= 1) mp[i] = x; dist[x] = nv; }
inline void del(int x) { for (mp[x += M] = 0, x >>= 1; x; mp[x] = cmp(mp[x << 1], mp[x << 1 | 1]), x >>= 1); }
inline void dijkstra(int s) {
    rep(i, 0, n) dist[i] = inf;
    build(n); modify(s, 0);
    rep(t, 1, n - 1) {
        int x = mp[1]; del(x);
        erp(i, x) if (dist[v[i]] > dist[x] + w[i])
            modify(v[i], dist[x] + w[i]);
    }
}

int main() {
    read(n, m, s);
    rep(i, 1, m) {
        int x, y, z; read(x, y, z); ae(x, y, z);
    }
    dijkstra(s);
    rep(i, 1, n) write(dist[i]), putchar(' ');
    puts("");
    return 0;
}

线段树大法好