Codeforces - 786B Legacy 线段树建图+最短路

Codeforces-786B Legacy 线段树建图+最短路


传送门: https://codeforces.com/contest/786/problem/B

题意

有 n 个 星 球 , q 条 路 径 , 问 从 一 个 起 点 为 s 的 星 球 到 其 他 星 球 的 距 离 。 有n个星球,q条路径,问从一个起点为s的星球到其他星球的距离。 nqs

每 条 路 径 可 能 有 以 下 三 种 形 式 : 每条路径可能有以下三种形式:

  • 1    u    v    w : 一 个 点 u 到 另 一 点 v , 路 程 为 w 1\;u\;v\;w:一个点u到另一点v,路程为w 1uvwuvw
  • 2    u    l    r    w : 从 一 点 u 到 [ l , r ] 区 间 任 意 一 个 星 球 , 路 程 为 w 2\;u\;l\;r\;w:从一点u到[l,r]区间任意一个星球,路程为w 2ulrwu[l,r]w
  • 3    u    l    r    w : 从 [ l , r ] 区 间 任 意 一 个 星 球 到 另 一 点 u , 路 程 为 w 3\;u\;l\;r\;w:从[l,r]区间任意一个星球到另一点u,路程为w 3ulrw[l,r]uw

思路

最短路问题,直接Dijkatra跑出来。不过这题考查的点是区间和点怎么建边。区间?线段树!!!

因为线段树里的一个点能代表一个区间,所以我们可以利用线段树建图。

因为有的操作是点连接区间,有的是区间连接点,所以一棵线段树是不够的,需要两棵线段树-一棵入树、一棵出树。

对于入树,我们认为他是区间连接点的线段树,所以叶子结点都要往父节点走,如图所示。
在这里插入图片描述

对于出树,我们认为他是点连接区间的线段树,所以父节点要往叶子结点走,如图所示。
在这里插入图片描述
这两棵线段树内部的边在建树的时候就建边,并且认为不需要路程。
而两棵树的叶子结点没有连,虽方便起见,我们可以整合成一幅图,如图所示:
在这里插入图片描述

我们默认边都是从下指向上的(统一)。
这样就方便我们的三个操作:
如果是点与点建边,将入树的叶子结点与出树的叶子结点相连。
如果是点与区间建边,将入树的叶子结点与出树的区间结点相连。
如果是区间与点建边,将入树的区间结点与入树的叶子结点相连。

最后跑一遍最短路即可。

Code(374MS)

#include "bits/stdc++.h"
using namespace std;

typedef long long ll;

const ll INF = 1e18;

const int maxn = 2e5 + 10;
const int N = 2e5 + 10;

struct Edge {
    int v, next;
    ll w;
}e[maxn << 4];
int head[maxn << 4], cnt;

inline void add(int u, int v, ll w) {
    e[++cnt].v = v;
    e[cnt].w = w;
    e[cnt].next = head[u];
    head[u] = cnt;
}

int idin[maxn << 4], idout[maxn << 4]; // 入树和出树上点的编号


#define lc u << 1
#define rc u << 1 | 1

struct Tree {
    int l, r, id;
}tin[N << 4], tout[N << 4];

int tot; // 动态开点内存池

void push_up_in(int u) { // 叶子结点向父节点连线
    add(tin[lc].id, tin[u].id, 0);
    add(tin[rc].id, tin[u].id, 0);
}

void push_up_out(int u) { // 父节点向叶子结点连线
    add(tout[u].id, tout[lc].id, 0);
    add(tout[u].id, tout[rc].id, 0);
}

// 入树
void build_in(int u, int l, int r) {
    tin[u].l = l; tin[u].r = r;
    tin[u].id = ++tot;
    if(l == r) {
        idin[l] = tin[u].id;
        return ;
    }
    int m = (l + r) >> 1;
    build_in(lc, l, m);
    build_in(rc, m + 1, r);
    push_up_in(u);
}

// 出树
void build_out(int u, int l, int r) {
    tout[u].l = l; tout[u].r = r;
    tout[u].id = ++tot;
    if(l == r) {
        idout[l] = tout[u].id;
        add(idout[l], idin[l], 0); // 两棵树的叶子结点以0距离连接
        return ;
    }
    int m = (l + r) >> 1;
    build_out(lc, l, m);
    build_out(rc, m + 1, r);
    push_up_out(u);
}

void modify_in(int u, int ql, int qr, int v, ll w) { // 点连接区间
    if(ql <= tout[u].l && tout[u].r <= qr) {
        add(idin[v], tout[u].id, w);
        return ;
    }
    int mid = (tout[u].l + tout[u].r) / 2;
    if(ql <= mid) modify_in(lc, ql, qr, v, w);
    if(qr > mid)  modify_in(rc, ql, qr, v, w);
}

void modify_out(int u, int ql, int qr, int v, ll w) { // 区间连接点
    if(ql <= tin[u].l && tin[u].r <= qr) {
        add(tin[u].id, idout[v], w);
        return ;
    }
    int mid = (tin[u].l + tin[u].r) / 2;
    if(ql <= mid) modify_out(lc, ql, qr, v, w);
    if(qr > mid)  modify_out(rc, ql, qr, v, w);
}

bool vis[maxn << 4];
ll dis[maxn << 4];

struct node {
    int now;
    ll d;
    bool operator < (const node &rhs) const {
        return d > rhs.d;
    }
};

priority_queue<node> q;

void Dij(int s) {
    dis[s] = 0;
    q.push({s, 0});
    while(!q.empty()) {
        node p = q.top();
        q.pop();
        int u = p.now;
        if(vis[u]) continue;
        vis[u] = true;
        for(int i = head[u]; i; i = e[i].next) {
            int v = e[i].v;
            if(dis[v] > dis[u] + e[i].w) {
                dis[v] = dis[u] + e[i].w;
                q.push({v, dis[v]});
            }
        }
    }
}

void solve() {
    int n, m, s; cin >> n >> m >> s;
    build_in(1, 1, n);
    build_out(1, 1, n);
    for(int i = 1;i <= m; i++) {
        int opt, l, r, u, v;
        ll w;
        cin >> opt;
        // 规定连接都是由下往上,入树的点连接出树的点
        if(opt == 1) {
            cin >> u >> v >> w;
            add(idin[u], idout[v], w);
        }
        else if(opt == 2) { // 点连接区间
            cin >> u >> l >> r >> w;
            modify_in(1, l, r, u, w);
        }
        else if(opt == 3) { // 区间连接点
            cin >> u >> l >> r >> w;
            modify_out(1, l, r, u, w);
        }
    }
    // 预处理所有点,线段树的叶子结点编号会超过n,,所以要多开几倍
    for(int i = 1;i <= 8 * n; i++) dis[i] = INF;
    Dij(idin[s]);
    for(int i = 1;i <= n; i++) {
        if(dis[idin[i]] != INF) cout << dis[idin[i]] << " ";
        else cout << -1 << " ";
    }
    cout << endl;
}

signed main() {
    solve();
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值