• 线段树优化建图


    线段树优化建图经常用来优化单点向某个区间连边,区间向单点连边的问题,可以将边数从 (qn) 级别降至 (q log n) 级别。

    具体地,从一道题来引入:CF786B Legacy

    先单独考虑从点 (u) 向区间 ([l, r]) 连边的操作。

    你会发现因为任意一个区间 ([l, r]) 在线段树上都可以被表示为 (log n) 个区间,我们可以尝试从 (u) 向这 (log n) 个区间直接连边,然后像加法一样打上一个懒标记。

    那么问题就在于如何下传这个懒标记,你会发现如果 (u) 可以走到 ([l, r]) 这个区间,那么一定可以走到 ([l, r]) 在线段树上的子区间。

    因此,我们可以事先将线段树上的每个区间向其子区间连一条有向边,那么懒标记的问题也就不存在了。

    再来考虑从区间 ([l, r]) 向单点 (u) 连边的操作。

    同样地,我们新开一颗线段树,每次将区间 ([l, r]) 在线段树上对应的 (log n) 个区间向 (u) 连一条有向边。

    但由于现在变成了一个区间的儿子区间都能走出去,因此我们预先需要从儿子区间向父亲区间连一条有向边。

    但是你会发现我们的单点 (u) 是还未定义的,还需要考虑这最后一点。

    一个最简单的想法就是直接开一排的点 (1 sim n),然后将这上述两个操作连到这 (n) 个点上。

    于此同时,因为实际上在我们的构造中出现了 (3) 个点均代表原图上的同一个点,但构造中这 (3) 个可能可以到达不同点。

    因此,我们还需要将这三个点联通。

    但实际上是不需要这样的,不难发现单点也可以被认为是线段树上的一个区间,因此可以直接用两个线段树的底层代表单点。

    同时,还是需要保证单点在图上的一致性,因此还需要将两个单点之间连上一条无向边。

    #include <bits/stdc++.h>
    using namespace std;
    #define ls t[p].l
    #define rs t[p].r
    #define mid (l + r >> 1)
    #define rep(i, l, r) for (int i = l; i <= r; ++i)
    #define Next(i, u) for (int i = h[u]; i; i = e[i].next)
    const int N = 400000 + 5;
    const int M = 30 + 5;
    const long long inf = 1e14;
    struct edge { int v, next, w;} e[N * M];
    struct tree { int l, r;} t[N];
    struct node { 
        int p; long long w;
        bool operator < (const node &x) const {
            return w > x.w;
        }
    };
    bool book[N];
    priority_queue <node> Q;
    long long dis[N];
    int n, q, l, r, u, v, w, s, opt, tot, cnt, rt[2], h[N], pl[2][N];
    int read() {
        char c; int x = 0, f = 1;
        c = getchar();
        while (c > '9' || c < '0') { if(c == '-') f = -1; c = getchar();}
        while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
        return x * f;
    }
    void add(int u, int v, int w, int type) { 
        if(!type) e[++tot].v = v, e[tot].w = w, e[tot].next = h[u], h[u] = tot;
        else e[++tot].v = u, e[tot].w = w, e[tot].next = h[v], h[v] = tot;
    }
    void build(int &p, int l, int r, int type) {
        p = ++cnt; if(l == r) { pl[type][l] = p; return;}
        build(ls, l, mid, type), build(rs, mid + 1, r, type);
        add(p, ls, 0, type), add(p, rs, 0, type);
    }
    void update(int p, int l, int r, int x, int y, int k, int w, int type) {
        if(l >= x && r <= y) { add(k, p, w, type); return;}
        if(mid >= x) update(ls, l, mid, x, y, k, w, type);
        if(mid < y) update(rs, mid + 1, r, x, y, k, w, type);
    }
    signed main() {
        n = read(), q = read(), s = read();
        build(rt[0], 1, n, 0), build(rt[1], 1, n, 1);
        rep(i, 1, n) add(pl[0][i], pl[1][i], 0, 0), add(pl[0][i], pl[1][i], 0, 1);
        rep(i, 1, q) {
            opt = read();
            if(opt == 1) u = read(), v = read(), w = read(), update(rt[0], 1, n, v, v, pl[1][u], w, 0);
            if(opt == 2) u = read(), l = read(), r = read(), w = read(), update(rt[0], 1, n, l, r, pl[1][u], w, 0);
            if(opt == 3) u = read(), l = read(), r = read(), w = read(), update(rt[1], 1, n, l, r, pl[0][u], w, 1);
        }
        rep(i, 1, cnt) dis[i] = inf;
        Q.push((node){pl[1][s], 0}), dis[pl[1][s]] = 0;
        while (!Q.empty()) {
            node u = Q.top(); Q.pop();
            if(book[u.p]) continue; book[u.p] = true;
            Next(i, u.p) {
                int v = e[i].v; 
                if(dis[v] > dis[u.p] + e[i].w) dis[v] = dis[u.p] + e[i].w, Q.push((node){v, dis[v]});
            }
        }
        rep(i, 1, n) printf("%lld ", dis[pl[0][i]] == inf ? -1 : dis[pl[0][i]]);
        return 0;
    }
    

    值得一提的是,线段树的优秀性质使得它能解决很大一部分设计区间的问题。

    比如:

    • 一个区间能在线段树上用 (log n) 个区间表示,这类问题通常拥有区间的包含性。换句话说,父亲区间满足的信息子区间也满足。

    • 线段树的可并性,这给一类单点修改问题很大的契机。比如:楼房重建

    • 线段总长度 (O(n log n)),这给一类区间存在性问题很暴力的解法。即通常某个信息存在于某个区间,可以暴力插入到线段树上的对应区间,查询就只需要访问 (log n) 个区间或最后一起访问。

    GO!
  • 相关阅读:
    Ubuntu11.04中如何将pycharm添加到系统的“应用程序”菜单里 (pycharm已成功安装)
    Office 365 离线安装
    关于wxpy,使用Python玩转微信的问题
    python3安装scrapy问题解决
    python打包exe文件-ImportError: No module named 'queue'
    Linux TOP命令按内存占用排序和按CPU占用排序
    设置iptables允许ssh、http、ftp服务
    重置linux mysql root密码
    linux.go
    return_fun.go 源码阅读
  • 原文地址:https://www.cnblogs.com/Go7338395/p/13832890.html
Copyright © 2020-2023  润新知