cf 786B

建立两个线段树,第一个线段树是维护边的起点,即出边,第二个线段树维护边的终点,即入边
同时对每个线段树上的点命名,叶子节点的命名用([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;
}
原文地址:https://www.cnblogs.com/Emcikem/p/13417892.html