建立两个线段树,第一个线段树是维护边的起点,即出边,第二个线段树维护边的终点,即入边
同时对每个线段树上的点命名,叶子节点的命名用([1,n])即可,其他用线段树建立的顺序命名。
比如对于第一棵线段树的建立
在建立过程中,同时add边权为0的边,比如编号(n + 1)的点到(n+2)的点建立边,边权为0,表示(n+1)的区间能到达(n + 2)的区间,花费0
同理,第二棵线段树也类似,只不过箭头是从下到上的
操作一时,从入树叶子节点向出树叶子节点连边(红色的线)。
操作二时,从入树叶子节点向出树所对应的区间节点连边(蓝色的线)。
操作三时,从入树所对应的区间节点向出树叶子节点连边(绿色的线)
对于一个点u,在区间([l,r])里,点v,在区间([x,y])里,且这四个两两存在边,那么对于从点u到v的方法有:
- 直接从u到v走
- 从u到([x,y]),再从([x,y])到v点
- 从u到([l,r]),再从([l,r])到v点
- 从u到([l,r]),再从([l,r])到([x,y]),最后从([x,y])到v点
#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#define ll long long
using namespace std;
const int N = 1e5 + 5;
const int M = 1e5 + 5;
// const int INF = 0x3f3f3f3f;
struct edge{
int next, to;
ll w;
}e[M * 30];
int head[N << 2], tot;
inline void add(int u, int v, ll w){//链式前向星存图
e[++tot].to = v;
e[tot].w = w;
e[tot].next = head[u];
head[u] = tot;
}
struct node{
ll dis;//dis表示到起始点的距离
int pos;//pos表示该点的下标
bool operator < (const node &x)const{//符号重载,对距离排序,针对于在优先队列里
return x.dis < dis;
}
};
ll dis[N << 2];
bool vis[N << 2];
void dijkstra(int s, int n){
memset(dis, 0x3f, sizeof(dis)); dis[s] = 0;
std::priority_queue<node> q;//构造优先队列
q.push((node){dis[s], s});//把初始位置和距离放进优先队列
while(!q.empty()){
node tmp = q.top();
q.pop();
int u = tmp.pos;
if(vis[u]) continue;//如果这个点走过了
vis[u] = 1;//标记走过
for(int i = head[u]; i; i = e[i].next){
int v = e[i].to;
if(!vis[v] && dis[v] > dis[u] + e[i].w){
dis[v] = dis[u] + e[i].w;
q.push((node){dis[v], v});
}
}
}
}
int cnt1, cnt2, cnt, ls[N << 2], rs[N << 2];
void build_out(int &p, int l, int r){ // 根编号是cnt1
if(l == r) { // 对于叶子结点,编号为[1,n]
p = l;
return;
}
int mid = (l + r) >> 1; p = ++cnt; // 非叶子结点编号[n + i]
build_out(ls[p], l, mid); build_out(rs[p], mid + 1, r);
add(p, ls[p], 0); add(p, rs[p], 0); // 大区间到小区间建立边
}
void build_in(int &p, int l, int r){ // 根编号是cnt2
if(l == r) {
p = l;
return;
}
int mid = (l + r) >> 1; p = ++cnt;
build_in(ls[p], l, mid); build_in(rs[p], mid + 1, r);
add(ls[p], p, 0); add(rs[p], p, 0);
}
void update1(int v, int p, int L, int R, int l, int r, int w){ // 点v到区间[l,r]
if(l <= L && R <= r) {
add(v, p, w);
return;
}
int mid = (L + R) >> 1;
if(l <= mid) update1(v, ls[p], L, mid, l, r, w);
if(r > mid) update1(v, rs[p], mid + 1, R, l, r, w);
}
void update2(int v, int p, int L, int R, int l, int r, int w){ //区间[l,r]到点v
if(l <= L && R <= r) {
add(p, v, w);
return;
}
int mid = (L + R) >> 1;
if(l <= mid) update2(v, ls[p], L, mid, l, r, w);
if(r > mid) update2(v, rs[p], mid + 1, R, l, r, w);
}
int main(){
int n, m, s;
scanf("%d%d%d", &n, &m, &s);
cnt = n;
build_out(cnt1, 1, n); build_in(cnt2, 1, n);
for(int i = 1; i <= m; i++) {
int op, v, l, r, u, w;
scanf("%d", &op);
if(op == 1) {
scanf("%d%d%d", &v, &u, &w);
add(v, u, w);
}
if(op == 2) {
scanf("%d%d%d%d", &v, &l, &r, &w);
update1(v, cnt1, 1, n, l, r, w); // 在出边找区间
}
if(op == 3) {
scanf("%d%d%d%d", &v, &l, &r, &w);
update2(v, cnt2, 1, n, l, r, w); // 在入边找区间
}
}
dijkstra(s, n);
for(int i = 1; i <= n; i++)
printf("%lld%c", dis[i] == dis[0] ? -1 : dis[i], "
"[i == n]);
return 0;
}