道路与航线(算法竞赛进阶指南,拓扑排序 + dijkstra)

一.题目链接:

道路与航线

二.题目大意:

T 个点,R 条双向边,P 条单向边.

其中双向边权值均为正,单向边权值可为负.

保证如果存在 a 到 b 的路径,则不存在 b 到 a 的路径.

求 S 到所有点的最短距离.

三.分析:

现场赛的时候没发现是蓝书原题...

直接 SPFA 会 TLE...

由于题目给出的特殊条件:双向边权值为正,单向边权值可负,并且单向边不存在环.

如果只把双向边插入图中,会得到若干个连通块,把每个块看成一个点,再加入单向边,则变成了 DAG.

由此我们可以先拓扑排序,在拓扑排序里面跑块内的 dijkstra. (由于要更新入度,SPFA 便不再适用.)

注意:由于有负权单向边的存在,最后答案判定的时候,不可以写 dis == inf.

因为有可能 S 无法到达的点被其他点更新.

四.代码实现:

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

const int M = 25000;
const int N = 50000 * 3;
const int inf = 0x3f3f3f3f;

int T, R, P, S;

int cnt;
int head[M + 5];
struct node
{
    int v, w, nx;
}Edge[N + 5];

int dis[M + 5];
bool vis[M + 5];

int in[M + 5];
int tot, block[M + 5];

void init()
{
    tot = cnt = 0;
    for(int i = 1; i <= T; ++i)
    {
        vis[i] = 0;
        head[i] = -1;
        dis[i] = inf;

        in[i] = 0;
        block[i] = 0;
    }
}

void add(int u, int v, int w)
{
    Edge[cnt].v = v;
    Edge[cnt].w = w;
    Edge[cnt].nx = head[u];
    head[u] = cnt++;
}

void dfs(int u)
{
    for(int i = head[u]; ~i; i = Edge[i].nx)
    {
        int v = Edge[i].v;
        if(!block[v])
        {
            block[v] = tot;
            dfs(v);
        }
    }
}

queue <int> topo_q;
priority_queue < pair<int, int> > dij_q;

void dijkstra(int s)
{
    for(int i = 1; i <= T; ++i)
    {
        if(block[i] == s)   dij_q.push(make_pair(-dis[i], i));
    }
    while(!dij_q.empty())
    {
        int u = dij_q.top().second;
        dij_q.pop();
        if(vis[u])  continue;
        vis[u] = 1;
        for(int i = head[u]; ~i; i = Edge[i].nx)
        {
            int v = Edge[i].v;
            if(dis[v] > dis[u] + Edge[i].w)
            {
                dis[v] = dis[u] + Edge[i].w;
                if(block[v] == s)   dij_q.push(make_pair(-dis[v], v));
            }
            if(block[v] != s && !--in[block[v]])    topo_q.push(block[v]);
        }
    }
}

void topo_sort()
{
    for(int i = 1; i <= tot; ++i)
    {
        if(!in[i])  topo_q.push(i);
    }
    while(!topo_q.empty())
    {
        int u = topo_q.front();
        topo_q.pop();
        dijkstra(u);
    }
}

int main()
{
    scanf("%d %d %d %d", &T, &R, &P, &S);
    init();
    for(int i = 1, u, v, w; i <= R; ++i)
    {
        scanf("%d %d %d", &u, &v, &w);
        add(u, v, w), add(v, u, w);
    }
    for(int i = 1; i <= T; ++i)
    {
        if(!block[i])
        {
            block[i] = ++tot;
            dfs(i);
        }
    }
    for(int i = 1, u, v, w; i <= P; ++i)
    {
        scanf("%d %d %d", &u, &v, &w);
        add(u, v, w);
        ++in[block[v]];
    }
    dis[S] = 0;
    topo_sort();
    for(int i = 1; i <= T; ++i)
    {
        if(dis[i] > 1e9)    printf("NO PATH\n");
        else                printf("%d\n", dis[i]);
    }
    return 0;
}

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值