ACWing 342 道路与航线 题解

ACWing 342 道路与航线 题解

宣传一下我的个人博客y0k1n0的小破站

题目描述

Farmer John 正在一个新的销售区域对他的牛奶销售方案进行调查。他想把牛奶送到 T T T 个城镇 ( 1 ≤ T ≤ 25 , 000 1 \le T \le 25,000 1T25,000 ),编号为 1 1 1 T T T 。这些城镇之间通过 R R R 条道路 ( 1 ≤ R ≤ 50 , 000 1 \le R \le 50,000 1R50,000 ,编号为 1 1 1 R R R ) 和 P P P 条航线 ( 1 ≤ P ≤ 50 , 000 1 \le P \le 50,000 1P50,000 ,编号为 1 1 1 P P P ) 连接。每条道路 i i i 或者航线 i i i 连接城镇 A i A_i Ai ( 1 ≤ A i ≤ T 1 \le A_i \le T 1AiT )到 B i B_i Bi ( 1 ≤ B i ≤ T 1 \le B_i \le T 1BiT ),花费为 C i C_i Ci

对于道路 0 ≤ C i ≤ 10 , 000 0 \le C_i \le 10,000 0Ci10,000 ;然而航线的花费很神奇,花费 C i C_i Ci 可能是负数( − 10 , 000 ≤ C i ≤ 10 , 000 -10,000 \le C_i \le 10,000 10,000Ci10,000 )。道路是双向的,可以从 A i A_i Ai B i B_i Bi,也可以从 B i B_i Bi A i A_i Ai ,花费都是 C i C_i Ci 。然而航线与之不同,只可以从 A i A_i Ai B i B_i Bi

事实上,由于最近恐怖主义太嚣张,为了社会和谐,出台 了一些政策保证:如果有一条航线可以从 A i A_i Ai B i B_i Bi,那么保证不可能通过一些道路和航线从 B i B_i Bi 回到 A i A_i Ai 。由于 F J FJ FJ 的奶牛世界公认十分给力,他需要运送奶牛到每一个城镇。他想找到从发送中心城镇 S S S ( 1 ≤ S ≤ T 1 \le S \le T 1ST) 把奶牛送到每个城镇的最便宜的方案,或者知道这是不可能的。

输入

R + P + 1 R+P+1 R+P+1

1 1 1 行:四个整数 T T T , R R R , P P P S S S ,分别表示城镇的数量,道路的数量,航线的数量和中心城镇。

2 2 2 R + 1 R+1 R+1 行:每行三个整数 A i A_i Ai , B i B_i Bi C i C_i Ci ,描述一条道路。

R + 2 R+2 R+2 R + P + 1 R+P+1 R+P+1 行:每行三个整数 A i A_i Ai , B i B_i Bi C i C_i Ci ,描述一条航线。

输出

T T T 行,第 i i i 行输出城市 S S S 到城市 i i i 的最小花费。如果不能到达,输出NO PATH

解题思路

拿到本题, 由于存在负权边, 首先考虑的解题思路为spfa。但实际测试发现, 这道题的数据卡掉了spfa, 因此需要考虑其他的解题方式。

注意到在题干中道路的边权均为正数, 而航道的边权可正可负, 因此考虑从这里入手。

题目中写道:**如果有一条航线可以从 A i A_i Ai B i B_i Bi,那么保证不可能通过一些道路和航线从 B i B_i Bi 回到 A i A_i Ai 。**这意味着如果除去所有的航线,则图会被分割为若干个由无限边构成的子图, 且 A i A_i Ai B i B_i Bi所在的子图一定不连通。下面给出证明:

  • 已知如果 A i A_i Ai B i B_i Bi通过一条单向边连接, 则不存在从 B i B_i Bi A i A_i Ai的路径,
    • 如果不存在另外的从 A i A_i Ai B i B_i Bi的路径, 则 A i A_i Ai B i B_i Bi所在的子图一定不连通
    • 如果存在另外的从 A i A_i Ai B i B_i Bi的路径, 则这条路径上一定存在单向边
      • 假设这条路径上不存在单向边, 则存在从 B i B_i Bi A i A_i Ai的路径, 此时与题干相矛盾
    • 去除掉这条路径上的单向边, 则 A i A_i Ai B i B_i Bi一定不连通
  • 因此, 如果去除掉所有的单向边, 得到的图一定是由若干个仅有无向边组成的强连通分量, 而每个强连通分量之间的连接是通过若干条有向边实现的

由于每个连通分量之间的连通方式都是单向链接, 因此满足拓扑序, 可以顺序处理每个连通分量, 在每个连通分量内部采用dijstra算法, 连通分量之间利用拓扑排序求解。

代码解释:

  • 在代码实现中对于下列数组的解释如下:

    • id数组存储了每个节点对应的连通分量的标识

    • group数组存储了每个连通分量的所有的节点

    • indegree数组存储了每一个连通分量的入度

  • 更新最短距离的整体过程

    • 首先先将起点 s s s的距离置为 0 0 0, 将 s s s加入优先队列
    • 然后寻找入度为 0 0 0的强连通分量, 将其入队
    • 当队列不空时, 取出队头对应的强连通分量, 将其内部的所有的节点加入优先队列
    • 执行堆优化的dijstra算法
      • 在算法执行过程, 更新节点的最小值时, 需要判断一下边的始点和终点是否相同,如果不相同还要将终点所在的强连通分量的入度减一, 当终点所在的强连通分量为0的时候, 将终点加入队列

AC代码

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <cstring>
#include <queue>
#define endl '\n'
using namespace std;
const int N = 2e5 + 10;
typedef pair<int, int> PII;
int n, r, p, s;
int h[N], e[N], ne[N], w[N], idx;
int cnt;
int id[N];
vector<int> group[N];
int dist[N];
bool st[N];
int indegree[N];
queue<int> q;

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

void dfs(int u, int idx)
{
    id[u] = idx;
    group[idx].push_back(u);
    for(int i = h[u]; i != -1; i = ne[i])
    {
        int j = e[i];
        if(st[j]) continue;
        st[j] = true;
        dfs(j, idx);
    }
}

void dijstra(int idx)
{
    priority_queue<PII, vector<PII>, greater<PII>> heap;
    for(auto t : group[idx])
    {
        heap.push({dist[t], t});
    }

    while(heap.size())
    {
        auto t = heap.top();
        heap.pop();
        int d = t.first, u = t.second;
        if(st[u]) continue;
        st[u] = true;
        for(int i = h[u]; i != -1; i = ne[i])
        {
            int j = e[i];
            if(id[u] != id[j])
            {
                indegree[id[j]] --;
                if(indegree[id[j]] == 0) q.push(id[j]);
            }
            if(dist[j] > dist[u] + w[i])
            {
                dist[j] = dist[u] + w[i];
                if(id[u] == id[j]) heap.push({dist[j], j});
            }
        }
    }
}

void topsort()
{
    memset(dist, 0x3f, sizeof dist);
    memset(st, 0, sizeof st);
    dist[s] = 0;
    for(int i = 1; i <= cnt; i ++)
    {
        if(indegree[i]) continue;
        q.push(i);
    }
    while(q.size())
    {
        int t = q.front();
        q.pop();
        dijstra(t);
    }
}

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
    memset(h, -1, sizeof h);
    
    cin >> n >> r >> p >> s;
    for(int i = 1; i <= r; i ++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
        add(b, a, c);
    }

    for(int i = 1; i <= n; i ++)
    {
        if(st[i]) continue;
        st[i] = true;
        dfs(i, ++ cnt);
    }

    // for(int i = 1; i <= n; i ++) cout << id[i] << ' ';

    for(int i = 1; i <= p; i ++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        add(a, b, c);
        indegree[id[b]] ++;
    }

    // for(int i = 1; i <= cnt; i ++) cout << indegree[i] << ' ';

    topsort();

    for(int i = 1; i <= n; i ++)
    {
        if(dist[i] > 0x3f3f3f3f / 2) cout << "NO PATH" << endl;
        else cout << dist[i] << endl;
    }

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值