[好题][缩点][拓扑排序][dijkstra]道路与航线 AcWing342

48 篇文章 0 订阅

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

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

这些城镇之间通过 R 条道路 (编号为 1 到 R) 和 P 条航线 (编号为 1 到 P) 连接。

每条道路 i 或者航线 i 连接城镇 Ai 到 Bi,花费为 Ci。

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

道路是双向的,可以从 Ai 到 Bi,也可以从 Bi 到 Ai,花费都是 Ci。

然而航线与之不同,只可以从 Ai 到 Bi。

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

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

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

输入格式

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

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

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

输出格式

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

数据范围

1≤T≤25000,
1≤R,P≤50000,
1≤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

题意: 一个n点的有向图,其中有r条道路和p条航线,道路是双向的且边权非负,航向是单向的且权值可能为负数,求从起点s到各点的最短距离。

分析: 这道题目可以用slf优化后的spfa水过去的,见[spfa][slf优化]道路与航线 AcWing342,但那毕竟不是正解,算是投机取巧的方法了,所以这里记录一下正解。

首先注意到题目中有一句很重要的话:保证如果有一条航线可以从 Ai 到 Bi,那么保证不可能通过一些道路和航线从 Bi 回到 Ai。这句话的信息量是相当大的,如果只把双向边加入图,那么就构成了若干个连通块,同时单向边会连接若干连通块,成为它们之间连接的桥梁,另外由于不存在单向边构成的环,如果把连通块缩成一个点,那么最终会形成一个有向无环图。

由于连通块内部边权都是正的,这些点的最短路可以跑dijkstra,连通块和连通块之间的距离则通过拓扑排序来求出。具体过程是对连通块进行拓扑排序,遍历当前连通块内部点,若存在点dis[i] != inf,则将其作为起点加入优先队列,然后在连通块内部跑dijkstra,之后再遍历内部点,如果存在点通过单向边连接到其它连通块,则用dis[i]+w更新一下dis[to],同时to所在连通块的入度减1,这里有一个细节,当dis[i]为inf时不可以更新,因为w可以为负数,dis[to]和dis[i]都是inf,此时更新会让dis[to]变小,这样最后统计无法到达的点时会很麻烦。

具体代码如下:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <string>
#include <vector>
#include <queue>
#define pii pair<int, int>
#define to first
#define w second 
#define inf 0x3f3f3f3f
using namespace std;

int n, m1, m2, s, fa[25005], in[25005], dis[25005];
bool vis[25005];
vector<pii> g1[25005], g2[25005];//g1道路图,g2航线图 
vector<int> ele[25005];//ele记录连通块内各点 

int find(int x)
{
	if(x == fa[x]) return x;
	return fa[x] = find(fa[x]);
}

void dijkstra(int id)//在id连通块内部跑最短路 
{
	priority_queue<pii, vector<pii>, greater<pii> > a;
	for(int i = 0; i < ele[id].size(); i++)//加入可能的起点 
		if(dis[ele[id][i]] != inf)
			a.push(make_pair(dis[ele[id][i]], ele[id][i]));
	while(a.size())
	{
		int now = a.top().second;
		a.pop();
		if(vis[now]) continue;
		vis[now] = true;
		for(int i = 0; i < g1[now].size(); i++)
		{
			int to = g1[now][i].to, w = g1[now][i].w;
			if(dis[to] > dis[now]+w)
			{
				dis[to] = dis[now]+w;
				a.push(make_pair(dis[to], to));
			}
		}
	}
}

signed main()
{
	cin >> n >> m1 >> m2 >> s;
	for(int i = 1; i <= n; i++)
		fa[i] = i;
	int u, v, w;
	for(int i = 1; i <= m1; i++)
	{
		scanf("%d%d%d", &u, &v, &w);
		g1[u].push_back(make_pair(v, w));
		g1[v].push_back(make_pair(u, w));
		int fu = find(u), fv = find(v);
		if(fu != fv) fa[fu] = fv;
	}
	for(int i = 1; i <= m2; i++)
	{
		scanf("%d%d%d", &u, &v, &w);
		int fu = find(u), fv = find(v);
		g2[u].push_back(make_pair(v, w));
		in[fv]++;
	}
	queue<int> q;//拓扑排序的队列 
	for(int i = 1; i <= n; i++)
	{
		dis[i] = inf;
		ele[find(i)].push_back(i); 
		if(fa[i] == i && in[i] == 0)
			q.push(i);
	}
	dis[s] = 0;
	while(q.size())
	{
		int now = q.front();
		q.pop(); 
		dijkstra(now);
		for(int i = 0; i < ele[now].size(); i++)//枚举连通块内点 
		{
			if(g2[ele[now][i]].size())
			{
				for(int j = 0; j < g2[ele[now][i]].size(); j++)
				{
					int to = g2[ele[now][i]][j].to, w = g2[ele[now][i]][j].w;
					if(dis[ele[now][i]] != inf)//很重要的细节,防止用inf更新其他连通块 
						dis[to] = min(dis[to], dis[ele[now][i]]+w);
					in[find(to)]--;
					if(in[find(to)] == 0)
						q.push(find(to));
				}
			}
		}
	}
	for(int i = 1; i <= n; i++)
		if(dis[i] != inf)
			printf("%d\n", dis[i]);
		else
			puts("NO PATH");
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值