[spfa][slf优化]道路与航线 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到各点的最短距离。

分析: 这道题很自然地可以想到用spfa,但是spfa会被网格图或菊花图卡成O(nm),用普通的spfa会T的很惨,同时由于出现了负边,不太能用dijkstra跑,其实硬要跑也是可以的,只不过不能用vis数组了,这会导致dijkstra的速度很慢,但其实它的速度仍然比被卡的spfa算法要快......

于是为了避免spfa被卡成O(nm)就出现了很多优化技巧,这里有一个比较简单的优化,那就是slf(small label first),也就是短边优先的规则,用一个双端队列保存结点,入队时根据dis[to]和dis[q.front()]的大小来决定入队头还是入队尾。

具体代码如下:  

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <string>
#include <deque>
//#define int long long
#define inf 0x3f3f3f3f
using namespace std;

int head[25005], cnt, vis[25005], dis[25005];
struct edge
{
	int to, next, w;
}e[150005];
int n, r, p, s;

void add(int u, int v, int w)
{
	e[++cnt].to = v;
	e[cnt].w = w;
	e[cnt].next = head[u];
	head[u] = cnt;
}

void spfa(int s)
{
	memset(dis, 0x3f, sizeof dis);
	deque<int> a;
	a.push_front(s);
	dis[s] = 0;
	while(a.size())
	{
		int now = a.front();
		a.pop_front();
		vis[now] = false;
		for(int i = head[now]; i; i = e[i].next)
		{
			int to = e[i].to;
			if(dis[to] > dis[now]+e[i].w)
			{
				dis[to] = dis[now]+e[i].w;
				if(!vis[to])
				{
					if(dis[to] > dis[a.front()])
					    a.push_back(to);
					else
					    a.push_front(to);
					vis[to] = true;
				}
			}
		}
	}
}

signed main()
{
	cin >> n >> r >> p >> s;
	for(int i = 1; i <= r; i++)
	{
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		add(u, v, w), add(v, u, w);
	}
	for(int i = 1; i <= p; i++)
	{
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		add(u, v, w);
	}
	spfa(s);
	for(int i = 1; i <= n; i++)
		if(dis[i] == inf)
			puts("NO PATH");
		else
			printf("%d\n", dis[i]);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值