Codeforces-786B Legacy 线段树建图+最短路
传送门: https://codeforces.com/contest/786/problem/B
题意
有 n 个 星 球 , q 条 路 径 , 问 从 一 个 起 点 为 s 的 星 球 到 其 他 星 球 的 距 离 。 有n个星球,q条路径,问从一个起点为s的星球到其他星球的距离。 有n个星球,q条路径,问从一个起点为s的星球到其他星球的距离。
每 条 路 径 可 能 有 以 下 三 种 形 式 : 每条路径可能有以下三种形式: 每条路径可能有以下三种形式:
- 1 u v w : 一 个 点 u 到 另 一 点 v , 路 程 为 w 1\;u\;v\;w:一个点u到另一点v,路程为w 1uvw:一个点u到另一点v,路程为w
- 2 u l r w : 从 一 点 u 到 [ l , r ] 区 间 任 意 一 个 星 球 , 路 程 为 w 2\;u\;l\;r\;w:从一点u到[l,r]区间任意一个星球,路程为w 2ulrw:从一点u到[l,r]区间任意一个星球,路程为w
- 3 u l r w : 从 [ l , r ] 区 间 任 意 一 个 星 球 到 另 一 点 u , 路 程 为 w 3\;u\;l\;r\;w:从[l,r]区间任意一个星球到另一点u,路程为w 3ulrw:从[l,r]区间任意一个星球到另一点u,路程为w
思路
最短路问题,直接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();
}