Legacy CodeForces - 787D (线段树优化建图+最短路)

\(Legacy\)

洛谷题目传送门

视频讲解

一、题目大意

二、解决思路

线段树优化建图的板子题

考虑暴力建图。显然不能通过此题。\(1≤n,q≤10^5,1≤w≤10^9\),数量太大!

这时候就需要用线段树优化建图了。线段树优化建图就是 利用线段树减少连边数量,从而降低复杂度。

基本思想

先建一棵线段树。假如现在我们要从 \(8\) 号点向区间 \([3,7]\) 的所有点连一条权值为 \(w\) 有向边。

那么怎么连边?把区间 \([3,7]\) 拆成 \([3,4]\)\([5,6]\)\([7,7]\) 然后分别连边。

就这样:(如下图所示。其中黑色普通边的边权为 \(0\),粉色边的边权为 \(w\)。)

原来我们要连 \(5\) 条边,现在只需要连 \(3\) 条边,也就是 \(⌈log_27⌉\) 条边。

于是 \(O(n)\) 的边数就优化成了 \(O(logn)\)

那么 操作三 (\(op=3\)) 用和 操作二 (\(op=2\)) 类似的方法连边。从区间 \([3,7]\) 的所有点向 \(8\) 号点连一条权值为 \(w\) 有向边:(其实就是边反了个方向)

以上是操作二与操作三分开来考虑的情形,那么操作二与操作三相结合该怎么办呢?

考虑建两棵线段树,第一棵只连自上而下的边,第二棵只连自下而上的边。方便起见,我们把第一棵树称作“出树”,第二棵树称作“入树”。

初始时自上而下或自下而上地在每个节点与它的父亲之间连边。由于两棵线段树的叶子节点实际上是同一个点,因此要在它们互相之间连边权为 \(0\) 的边。初始时是这样的:

接下来:

  • 对于操作一,就从入树的叶子节点向出树的叶子节点连边。
  • 对于操作二,就从入树的叶子节点向出树中的对应区间连边。
  • 对于操作三,就从入树中的对应区间向出树中的叶子节点连边。

举个栗子。比如现在我们要从 \(8\) 号点向区间 \([3,7]\) 的所有点连一条权值为 \(w\) 有向边。那么就如图所示连边:(为了让图更清楚,图中把入树和出树叶子节点之间相连的边省略了。)

三、代码实现

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const int N = 100100 << 3, M = N << 2;
typedef pair<LL, int> PII;

//最短路径
LL d[N]; //最短距离数组
bool st[N];

//邻接表
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}

struct Node {
    int l, r;
    int ls, rs; //左儿子编号,右儿子编号
} tr[N];

int rtin, rtout, cnt; //出树根编号,入树根编号,编号计数器(从n+1开始,前面n个留给真实节点)

// l:左边界, r:右边界, 返回新创建的节点编号
int build1(int l, int r) {
    if (l == r) {
        tr[l] = {l, r}; // tr[l]:叶子节点比较牛B,不用计数器生成节点号,而是真实的[1~n]。这样才能真正实现最底层的重复利用,防止重复建设
        return l;
    }
    int u = ++cnt;  //非叶子节点利用cnt获取节点号
    tr[u] = {l, r}; //记录区间范围
    int mid = (l + r) >> 1;
    tr[u].ls = build1(l, mid);     //构建左子树
    tr[u].rs = build1(mid + 1, r); //构建右子树
    //父节点向左右儿子连权值为0的边
    add(u, tr[u].ls, 0), add(u, tr[u].rs, 0);
    //返回新创建的节点编号
    return u;
}

int build2(int l, int r) {
    if (l == r) {
        tr[l] = {l, r};
        return l;
    }
    int u = ++cnt;
    tr[u] = {l, r};
    int mid = (l + r) >> 1;
    tr[u].ls = build2(l, mid);
    tr[u].rs = build2(mid + 1, r);
    //左右儿子向父节点连权值为0的边
    add(tr[u].ls, u, 0), add(tr[u].rs, u, 0);
    return u;
}
/**
 * @brief 点向区间连边 或 区间向点连边
 *
 * @param u 线段树根节点u
 * @param x 需要连边的点
 * @param l 区间左边界
 * @param r 区间右边界
 * @param w 权值
 */
void add1(int u, int x, int l, int r, int w) {
    if (l <= tr[u].l && tr[u].r <= r) {
        add(x, u, w);
        return;
    }
    if (l <= tr[tr[u].ls].r) add1(tr[u].ls, x, l, r, w);
    if (r >= tr[tr[u].rs].l) add1(tr[u].rs, x, l, r, w);
}

void add2(int u, int x, int l, int r, int w) {
    if (l <= tr[u].l && tr[u].r <= r) {
        add(u, x, w);
        return;
    }
    if (l <= tr[tr[u].ls].r) add2(tr[u].ls, x, l, r, w);
    if (r >= tr[tr[u].rs].l) add2(tr[u].rs, x, l, r, w);
}

void dijkstra(int s) {
    memset(d, 0x3f, sizeof(d));
    d[s] = 0;
    priority_queue<PII, vector<PII>, greater<PII>> q;
    q.push({0, s});
    while (q.size()) {
        int u = q.top().second;
        q.pop();
        if (st[u]) continue;
        st[u] = true;
        for (int i = h[u]; ~i; i = ne[i]) {
            int j = e[i];
            if (d[j] > d[u] + w[i]) {
                d[j] = d[u] + w[i];
                q.push({d[j], j});
            }
        }
    }
}

int main() {
    //加快读入
    ios::sync_with_stdio(false), cin.tie(0);
    memset(h, -1, sizeof h);
    int n, q, s;
    cin >> n >> q >> s;

    cnt = n;              //可用的合法编号从n+1开始
    rtin = build1(1, n);  //构建入树
    rtout = build2(1, n); //构建出树

    int op, x, y, l, r, w;
    for (int i = 1; i <= q; i++) {
        cin >> op;
        if (op == 1) { //点x到点y有一条边权w的边
            cin >> x >> y >> w;
            add(x, y, w);
        } else if (op == 2) { //点x到区间[l,r]有一条边权w的边
            cin >> x >> l >> r >> w;
            add1(rtin, x, l, r, w);
        } else if (op == 3) { //区间[l,r]到点x有一条边权w的边
            cin >> x >> l >> r >> w;
            add2(rtout, x, l, r, w);
        }
    }
    //最短路径
    dijkstra(s);

    //输出最短路
    for (int i = 1; i <= n; i++)
        printf("%lld ", d[i] == INF ? -1 : d[i]);
    putchar('\n');

    return 0;
}
posted @ 2022-04-26 17:12  糖豆爸爸  阅读(71)  评论(0编辑  收藏  举报
Live2D