题解:《算法竞赛进阶指南》道路与航线

农夫约翰正在一个新的销售区域对他的牛奶销售方案进行调查。

他想把牛奶送到 TT 个城镇,编号为 1∼T1∼T。

这些城镇之间通过 RR 条道路 (编号为 11 到 RR) 和 PP 条航线 (编号为 11 到 PP) 连接。

每条道路 ii 或者航线 ii 连接城镇 AiAi 到 BiBi,花费为 CiCi。

对于道路,0≤Ci≤10,0000≤Ci≤10,000;然而航线的花费很神奇,花费 CiCi 可能是负数(−10,000≤Ci≤10,000−10,000≤Ci≤10,000)。

道路是双向的,可以从 AiAi 到 BiBi,也可以从 BiBi 到 AiAi,花费都是 CiCi。

然而航线与之不同,只可以从 AiAi 到 BiBi。

事实上,由于最近恐怖主义太嚣张,为了社会和谐,出台了一些政策:保证如果有一条航线可以从 AiAi 到 BiBi,那么保证不可能通过一些道路和航线从 BiBi 回到 AiAi。

由于约翰的奶牛世界公认十分给力,他需要运送奶牛到每一个城镇。

他想找到从发送中心城镇 SS 把奶牛送到每个城镇的最便宜的方案。

输入格式

第一行包含四个整数 T,R,P,ST,R,P,S。

接下来 RR 行,每行包含三个整数(表示一个道路)Ai,Bi,CiAi,Bi,Ci。

接下来 PP 行,每行包含三个整数(表示一条航线)Ai,Bi,CiAi,Bi,Ci。

输出格式

第 1..T1..T 行:第 ii 行输出从 SS 到达城镇 ii 的最小花费,如果不存在,则输出 NO PATH

数据范围

1≤T≤250001≤T≤25000,
1≤R,P≤500001≤R,P≤50000,
1≤Ai,Bi,S≤T1≤Ai,Bi,S≤T

输入样例:

6 3 3 4
1 2 5
3 4 5
5 6 10
3 5 -100
4 6 -100
1 3 -10

输出样例:

NO PATH
NO PATH
5
0
-95
-100

大概思路:
将所有双向道路变为一个连通块,对于每个单向道路的边连接的都是连通块;
因此该图转化为一个拓扑图,在每个连通块中求dijkstra,每个单向道路中用
拓扑排序,最后dist[i]即为答案; 

解题思路:
1:先输入所有双向道路,再用dfs求出所有双向道路组成的连通块
id[N]记录该点N在哪个连通块中, block[N]记录在连通块N中的点有多少个
2:输入所有航线同时统计每个连通块的入度
3:遍历所有连通块,将入度为0的点所在的连通块放入队列q中
4:对于放入队列q中的连通块将每个连通块中的所有的点用dijkstra求最短路
5:每次从dijkstra的堆中取出距离最小的点,然后枚举他的下一个点,
若id[ver] == id[j]则表示他们在同一个连通块中继续枚举;
若id[ver] != id[j]说明他们在不同的连通块中,id[j]连通块的入度要减一;
若--din[id[j]] == 0则将他放入拓扑排序的q中 

#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 25010, M = 150010, INF = 0x3f3f3f3f;

int bcnt;
int id[N];
int din[N];
bool st[N];
int dist[N];
queue<int> q;
int n, mr, ml, S;
vector<int> block[N];
int h[N], e[M], ne[M], w[M], idx;

void add(int a, int b, int c)
{
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx;
    idx ++ ;
}

void dfs(int u, int bid)
{
    id[u] = bid;//记录点u在bid这个连通块中
    block[bid].push_back(u);//记录bid这个连通块中存在点u
    
    for (int i = h[u]; i != - 1; i = ne[i])
    {
        int j = e[i];
        if (!id[j])//继续枚举该联通块中的其他点
            dfs(j, bid);
    }
}

void dijkstra(int bid)
{
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    
    for (auto ver : block[bid]) heap.push({dist[ver], ver});//将bid这个连通块中的所有点入队
    
    while (heap.size())
    {
        auto t = heap.top();
        heap.pop();
        
        int distance = t.x, ver = t.y;
        
        if (st[ver]) continue;
        st[ver] = true;
        
        for (int i = h[ver]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (id[j] != id[ver] && -- din[id[j]] == 0) q.push(id[j]);//若id[ver] != id[j]说明他们在不同的连通块中,id[j]连通块的入度要减一;
                                                                      //若--din[id[j]] == 0则将他放入拓扑排序的q中
            if (dist[j] > dist[ver] + w[i])
            {
                dist[j] = dist[ver] + w[i];
                if (id[j] == id[ver]) heap.push({dist[j], j});//说明他们在同一个连通块中,继续枚举下一个数
            }
        }
    }
}

void topsort()
{
    memset(dist, 0x3f, sizeof dist);//初始化距离
    dist[S] = 0;
    
    for (int i = 1; i <= bcnt; i ++ )
        if (!din[i]) q.push(i);//枚举每个连通块,加入入度为0的点
    
    while (q.size())
    {
        auto t = q.front();
        q.pop();
        
        dijkstra(t);//对于t这个连通块,用dijkstra求出连通块中的最短路
    }
}

int main()
{
    cin >> n >> mr >> ml >> S;
    
    memset(h, -1, sizeof h);
    
    while (mr -- )
    {
        int a, b, c;
        scanf("%d %d %d", &a, &b, &c);
        add(a, b, c), add(b, a, c);
    }
    
    for (int i = 1; i <= n; i ++ )//枚举所有的连通块
        if (!id[i])
            dfs(i, ++ bcnt);
    
    while (ml -- )
    {
        int a, b, c;
        scanf("%d %d %d", &a, &b, &c);
        add(a, b, c);
        din[id[b]] ++ ;//将每个b所在的连通块的的入度++
    }
    
    topsort();
    
    for (int i = 1; i <= n; i ++ )
        if (dist[i] > INF / 2) puts("NO PATH");//因为存在负边,所以dist[j] > dist[i] + w[i]若w[i]
        else printf("%d\n", dist[i]);         //是负数,则无穷大dist[j]也能更新无穷大dist[i]因此要判断
                                              //它是不是大于一个极大的数表示无解而不是 是否等于INF;
    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

啥也不会hh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值