SPFA(Shortest Path Fast Algorithm)

某已死算法
关于SPFA,他已经死了

模板

int spfa()
{
	memset(dist,0x3f,sizeof dist);
	dist[1] = 0;
    queue<int> q;
    q.push(1);
    st[1] = true;
    while(q.size())
    {
		int t = q.front();
        q.pop();
        st[t] = false;
        for(int i = h[t];i != -1;i ++)
        {
			int j = e[i];
            if(dist[j] > dist[t] + w[i])
            {
				dist[j] = dist[t] + w[i];
                if(!st[j])
                {
					st[j] = true;
                    q.push(j);
                }
            }
        }
    }
    return dist[n];
}

判负环

bool spfa()
{
	memset(dist,0x3f,sizeof dist);
	dist[1] = cnt[1] = 0;
    queue<int> q;
    q.push(1);
    st[1] = true;
    while(q.size())
    {
		int t = q.front();
        q.pop();
        st[t] = false;
        for(int i = h[t];i != -1;i ++)
        {
			int j = e[i];
            if(dist[j] > dist[t] + w[i])
            {
				dist[j] = dist[t] + w[i];
                cnt[j] = cnt[t] + 1;
                if(cnt[j] >= n) return true;
                if(!st[j])
                {
					st[j] = true;
                    q.push(j);
                }
            }
        }
    }
    return false;
}

流程

spfa 可以看作是 bellman_ford 的队列优化,先看一下bellman_ford;
先介绍 Bellman-Ford 算法要用到的松弛操作。

对于边 ,松弛操作对应下面的式子:\(dist(v) = min(dist(v),dist(u) + w(u,v))\)

这么做的含义是显然的:我们尝试用 $S -> u - >v $ (其中 \(S\) 的路径取最短路)这条路径去更新 \(v\) 点最短路的长度,如果这条路径更优,就进行更新。

Bellman-Ford 算法所做的,就是不断尝试对图上每一条边进行松弛。我们每进行一轮循环,就对图上所有的边都尝试进行一次松弛操作,当一次循环中没有成功的松弛操作时,算法停止。

每次循环是 \(o(m)\) 的,那么最多会循环多少次呢?

在最短路存在的情况下,由于一次松弛操作会使最短路的边数至少 ,而最短路的边数最多为 \(n - 1\),因此整个算法最多执行 轮松弛操作。故总时间复杂度为 \(o(nm)\)

但还有一种情况,如果从 \(s\) 点出发,抵达一个负环时,松弛操作会无休止地进行下去。注意到前面的论证中已经说明了,对于最短路存在的图,松弛操作最多只会执行 \(n - 1\) 轮,因此如果第 \(n\) 轮循环时仍然存在能松弛的边,说明从 \(s\) 点出发,能够抵达一个负环。

不难发现,其实对于一个刚刚被松弛的点\(v\),只有他的出边才会引起松弛操作,而访问其他的点是没有必要的,所以可以用一个队列来维护所有有必要维护的点,只对有必要做松弛操作的点进行操作,由此spfa就诞生了。

技巧

一般适合写环路问题的题那么就要好好理解一下判负环模板中cnt数组的含义
[problem:LightOJ-1074]
‎达卡市日复一日地变得拥挤和嘈杂。某些道路总是因拥堵而受阻。为了说服人们避开最短的路线,从而避开拥挤的道路,市政府制定了新的计划。‎

‎城市的每个交汇点都标有一个正整数 ‎‎(n ≤ 20),‎‎表示交汇点的繁忙程度。每当有人从一个交汇点(源交汇点)到另一个交汇点(目的地交汇点)时,市政当局就会从旅行者那里获得数量‎‎(目的地的忙碌程度 - 源的忙碌程度)‎‎3‎‎(这意味着差异的立方体)。‎

‎现在,当局已经指定您找出当智能人从某个路口(零点)到其他几个路口时可以获得的最低总金额。‎

‎输入‎

‎输入以整数 ‎‎T 开头(≤ 50),‎‎表示测试用例的数量。‎

‎每个事例都包含一个空行和一个整数 ‎‎n(1 < n ≤ 200),‎‎表示交汇点的数量。下一行包含 ‎‎n‎‎ 个整数,分别表示从 ‎‎1‎‎ 到 ‎‎n‎‎ 的交汇点的繁忙程度。‎

‎下一行包含一个整数 ‎‎m‎‎,即城市中的道路数。接下来的每条 ‎‎m‎‎ 线(每条道路一条)都包含两个交汇点编号(源、目的地),相应的道路连接这两条交汇点编号(所有道路都是单向的)。下一行包含整数 ‎‎q‎‎,即查询数。接下来的 ‎‎q‎‎ 行每条都包含一个目标交汇点号。从一个交叉路口到另一个交叉路口最多只能有一条直接道路。‎

‎输出‎
‎对于每个案例,请在一行中打印Case编号。然后‎
‎打印 ‎‎q‎‎ 行,每个查询一条,每条线都包含从交汇点 ‎‎1‎‎(零点)到给定‎
‎交汇点时的最小总‎
‎收入。但是,对于总收入小于 ‎‎3‎‎ 的查询,‎
‎或者如果无法从零点到达目标,请打印 .‎?

输入示例

2

5
6 7 8 9 10
6
1 2
2 3
3 4
1 5
5 4
4 5
2
4
5

2
10 10
1
1 2
1
2

输出示例

Case 1:
3
4
Case 2:
?

分析
先抽象出题意:给定有个有向图,有q次询问,每次询问给出一个点问从1号点能否走到这个点且最小花费不小于3。这道题的边权计算有点意思,这里,我记\(p(u)\)为点u的忙碌程度\(p(v)\)为点v的忙碌程度,根据题意对于有向边 \(u -> v\) 的边权为\((p(v) - p(u)) ^ 3\),不难看出边权可为负,用spfa,同时我们思考这样一个问题,假若从起点到终点的路径中存在负环,那么,终点的最小花费一定可以任意小,可以小于3,那么必不符合题意,所以应输出”?“,
同样的,如果从起点根本走不到询问的点,那么也要输出"?"。
这里就不得不深究一下cnt数组的含义了,cnt[i]:从起点到 i 点的最短路径中边的数量。那么如果一条路径不存在环(也就是一个链),那么边数一定是节点数 - 1,如果存在环,边数一定 >= 节点数,我们只要用普通spfa来维护cnt数组即可

ac代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#include<map>
#define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#define endl '\n'
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;

const int N = 210,M = 200010,INF = 0x3f3f3f3f;
const double INFF = 0x7f7f7f7f7f7f7f7f;

int n,m,q;
int k[N];
int dist[N];
int cnt[N];
bool st[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 ++;
}


void spfa()
{
	memset(cnt,0,sizeof cnt);
	memset(st,0,sizeof st);
	memset(dist,0x3f,sizeof dist);
	dist[1] = 0;
	st[1] = true;
	queue<int> q;
	q.push(1);
	while(q.size())
	{
		int t = q.front();
		q.pop();
		st[t] = false;
		for(int i = h[t];i != -1;i = ne[i])
		{
			int j = e[i];
			if(dist[j] > dist[t] + w[i])
			{
				dist[j] = dist[t] + w[i];
				cnt[j] = cnt[t] + 1;
				if(!st[j] && cnt[j] < n)//注意这里cnt[j] < n 才能入队的判断,如果不加上,负环上的点就会无限次入队,导致死循环
				{
					st[j] = true;
					q.push(j);
				}
			}
		}
	}
}

int t ;
void solve()
{
	
	memset(h,-1,sizeof h);
	idx = 0;
	cin >> n;
	for(int i = 1;i <= n;i ++) cin >> k[i];
	cin >> m;
	while(m --)
	{
		int a,b;
		cin >> a >> b;
		add(a,b,(k[b] - k[a]) * (k[b] - k[a]) * (k[b] - k[a]));
	} 
	spfa();
	cin >> q;
	cout << "Case " << ++ t << ":" << endl; 
	while(q --)
	{
		int p;
		cin >> p;
		if(cnt[p] >= n || dist[p] < 3 || dist[p] == INF) cout << "?" << endl;
		else cout << dist[p] << endl;
	}
}

int main()
{
	ios;
	int t;
	cin >> t;
	while(t --)
	{
		solve(); 
	}
	return 0;
}
posted @ 2022-05-03 15:04  notyour_young  阅读(53)  评论(0编辑  收藏  举报