道路与航线

道路与航线

P3008 [USACO11JAN]Roads and Planes G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

题意:图中有两种路一种是单向可正可负,一种是双向不负,要求单源最短路。保证单向边不形成环。

拓扑序+堆优化dij

相当于每个双向边形成团,将团缩点后形成拓扑图,根据拓扑图性质可以线性求出每个点的最短路,团内部堆优化dij。

  1. 对所有双向边,找出连通块,即缩点;同时记录每个点的连通块标号和每个标号连通块有哪些点。
  2. 对所有单向边,形成拓扑图处理;记录所有连通块的入度。
  3. 先将每个入度为0的连通块加入队列,每次从队头取出一个连通块,将连通块中的所有点进行堆优化dij,每次取出堆中距离最小的点,对这个点能扩展到的点如果属于同一个连通块就更新,这里就是普通堆优化dij的操作,如果不属于同一个连通块,将这个连通块的入度减一,判断如果入度为0则将连通块加入队列。

注意dij的时候每个点只做一次,因为拓扑图连通块直接的连边可能为负一个点如果做好多次就会反复更新。

一道简单题写半天

#include<bits/stdc++.h>

using namespace std;

const int N = 25010;
const int inf = 0x3f3f3f3f;

typedef pair<int, int> pii;

struct node {
    int to, len;
};

vector<node> g[N];

int dist[N];

int idx = 0, id[N], du[N];
bool vis[N];
vector<int> block[N];
queue<int> tuopu;
priority_queue<pii, vector<pii>, greater<pii>> dijque;
bool book[N];

void dij() {

    while (!dijque.empty()) {
        pii tmp = dijque.top();
        dijque.pop();
        int u = tmp.second;
        if(book[u]) continue;
        book[u] = 1; 
        int siz = g[u].size();
        for (int i = 0; i < siz; ++i) {
            int to = g[u][i].to;
            int len = g[u][i].len;

            if (id[to] != id[u]) {
                dist[to] = min(dist[to], dist[u] + len);
                du[id[to]]--;
                if (du[id[to]] == 0) tuopu.push(id[to]);
                continue;
            }
            if (dist[to] < dist[u] + len) continue;
            dist[to] = dist[u] + len;
            dijque.push({dist[to], to});
        }
    }
}


void getBlocks(int s) {
    int nwid = ++idx;
    queue<int> que;
    que.push(s);
    vis[s] = 1;
    id[s] = nwid;
    block[nwid].push_back(s);

    while (!que.empty()) {
        int tmp = que.front();
        que.pop();
        int siz = g[tmp].size();
        for (int i = 0; i < siz; ++i) {
            int to = g[tmp][i].to;
            if (vis[to]) continue;
            vis[to] = 1;
            id[to] = nwid;
            block[nwid].push_back(to);
            que.push(to);
        }
    }
}

int main() {

    int n, r, p, s;
    scanf("%d%d%d%d", &n, &r, &p, &s);

    for (int i = 1, x, y, len; i <= r; ++i) {
        scanf("%d%d%d", &x, &y, &len);
        g[x].push_back({y, len});
        g[y].push_back({x, len});
    }
    for (int i = 1; i <= n; ++i) {
        if (vis[i]) continue;
        getBlocks(i);
    }
    for (int i = 1, x, y, len; i <= p; ++i) {
        scanf("%d%d%d", &x, &y, &len);
        g[x].push_back({y, len});
        du[id[y]]++;
    }

    memset(dist, 0x3f, sizeof dist);
    dist[s] = 0;

//    dij(s);
    for (int i = 1; i <= idx; ++i) {
        if (du[i] == 0) tuopu.push(i);
    }

    while (!tuopu.empty()) {
        int id = tuopu.front();
        tuopu.pop();

        for (int u : block[id]) {
            dijque.push({dist[u], u});
        }

        dij();
    }

    for (int i = 1; i <= n; ++i) {
        if (dist[i] > inf / 2) printf("NO PATH\n");
        else printf("%d\n", dist[i]);
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值