在上两篇博客中说了Dijkstra算法,求的是单源最短路,但是这个限制就是图中不存在负权边,因为当时咱们是通过反证法推出来的Dijkstra算法成立,因为如果还有比这个dis[i]还小的最短路的话一定是从这个地方拐过去的,但是边权又都是正的,只会越拐越大,所以就证明了Dijkstra算法的正确性,但是如果存在负权回路的话,他就不能推出来这个结论,因为会越拐越小,那么在有负权边的图中Dijkstra算法是错误的,一般来说Dijkstra算法能做的题目spfa算法也能做,应该也是能过的。
Bellman_ford
现在就来介绍一下bellman_ford算法,bellman_ford算法的做法就是遍历n-1次所有的边,然后每次都更新一下dist数组,最后遍历完之后所有点的dist都满足dist[i] < dis[a] + w[a,i],这个不等式也被称为三角不等式,更新的操作叫松弛操作,如果有负权回路的话最短路是不一定存在的,因为他会通过这个负环反向刷分,如果第n次循环有更新的话证明是有负环的,因为到了n-1次就已经找到最短路了,如果还有更新的话就是有负环了。在Bellman_ford算法中迭代k次求出的dist代表的是最多经过k条边的最短路径,如果迭代到第n次还有点的最短路更新的话,根据抽屉原理一定会有负环的。
spfa
spfa算法是基于Bellman_ford算法的思想然后用宽搜来进行优化,然后通过队列来进行操作的,具体思想就是这个点被更新了,他才有可能会更新别人,在稀疏图中spfa算法的时间复杂度也是比较可观的,是O(nk),这个k一般在稀疏图里面是常数级别的,大概不会超过2,但是有时候这个算法很慢,可以换成栈来试试,比如说下面有一个题
Wormholes
虫洞是星系间的时空隧道。借助虫洞,我们可以从一个星系抵达另一个星系,同时穿越至若干年后或若干年前。
研究发现:
虫洞是单向的。
穿越虫洞所需的时间可以忽略不计。
两个星系间最多存在一个虫洞。
虫洞的始末两端不可能在同一星系内。
由太阳系(编号为0)出发,总是能够通过虫洞到达其他星系。
科学家们计划利用虫洞回到宇宙诞生之际。请你帮忙判断,这一想法能否实现。
输入格式
本题有多组数据。
第一行一个正整数 TT,表示数据组数。
对于每组数据:
第一行三个整数 n,mn,m,分别表示星系的数目和虫洞的数目。星系编号分别为 0\dots n-10…n−1。
接下来 mm 行,每行三个整数 x,y,tx,y,t,表示存在一个由星系 xx 通往星系 yy 的虫洞,且通过该虫洞将穿越至未来的第 tt 年(若 tt 为负数则表示回到过去)。
输出格式
共 TT 行。对于每组数据,若科学家们的想法可能实现,则输出一行possible,否则输出一行not possible。
说明/提示
这个虫洞可以回到以前的时间点,可以把每个星系都看成一个点,然后两个点之间的边权就是从一个点走到另一个点所需要的时间,能不能回到宇宙诞生之际就可以转化成能不能从某个星系出发逛一圈回到这个时间以前,那么又能转化成存不存在负环,这个题用spfa算法就迎刃而解了,具体看代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 510,M = 2510*2,W = 210;
int w[N][N];
bool st[N];
int cnt[N],dis[N];
int n,m,f;
queue<int> q;
bool spfa() {
memset(dis, 0x3f, sizeof dis);
while(!q.empty()) q.pop();
for (int i = 1; i <= n; i++) {
q.push(i);
st[i] = true;
}
while (q.size()) {
int t = q.front();
q.pop();
st[t] = false;
for(int i=1;i<=n;i++){
if(w[t][i] != 0x3f3f3f3f){
if(dis[i] > dis[t] + w[t][i]){
dis[i] = dis[t] + w[t][i];
cnt[i] = cnt[t] + 1;
if(cnt[i] >= n) return true;
if(!st[i]){
st[i] = true;
q.push(i);
}
}
}
}
}
return false;
}
int main() {
int T;
scanf("%d",&T);
while(T--) {
scanf("%d%d%d",&n,&m,&f);
memset(cnt,0,sizeof cnt);
memset(w,0x3f,sizeof w);
for(int i=1; i<=m; i++) {
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
if(w[a][b] > c){
w[a][b] = w[b][a] = c;
}
}
for(int i=1; i<=f; i++) {
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
c = -c;
if(w[a][b] > c) w[a][b] = c;
}
if(spfa()) puts("YES");
else puts("NO");
}
return 0;
}
注意
有时候用队列也会超时,y总说过要是队列超时的话不妨换成栈再试一试,下面再看一个题
糖果
幼儿园里有 N 个小朋友, lxhgww 老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。
但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候, lxhgww 需要满足小朋友们的 K 个要求。
幼儿园的糖果总是有限的, lxhgww 想知道他至少需要准备多少个糖果,才能使得每个小朋友都能够分到糖果,并且满足小朋友们所有的要求。
输入格式
输入的第一行是两个整数 N , K 。
接下来 K 行,表示这些点需要满足的关系,每行 3 个数字, x , A , B 。
如果 X=1 .表示第 A 个小朋友分到的糖果必须和第 B 个小朋友分到的糖果一样多。
如果 X=2 ,表示第 A 个小朋友分到的糖果必须少于第 B 个小朋友分到的糖果。
如果 X=3 ,表示第 A 个小朋友分到的糖果必须不少于第 B 个小朋友分到的糖果。
如果 X=4 ,表示第 A 个小朋友分到的糖果必须多于第 B 个小朋友分到的糖果。
如果 X=5 ,表示第 A 个小朋友分到的糖果必须不多于第 B 个小朋友分到的糖果。
输出格式
输出一行,表示 lxhgww 老师至少需要准备的糖果数,如果不能满足小朋友们的所有要求,就输出 −1 。
样例
Input
5 7
1 1 2
2 3 2
4 4 1
3 4 5
5 4 5
2 3 5
4 5 1
Output
11
数据范围与提示
对于 30% 的数据,保证 N<100 。
对于 100% 的数据,保证 N<100000,K≤100000,1≤X≤5,1≤A,B≤N 。
下面看一个图
注意看spfa函数里面的使用数组模拟的队列,这是个循环队列,就是当hh或者是tt超过了数组大小的时候他就自动赋值成为0,但是如果用栈的话就不一样了
用栈的话就不用那么花里胡哨了,只要是栈顶指针不指向负数就行,这样就AC了,这个spfa算法的时间快慢跟建边的顺序很有关系有时候栈快,有时候队列快,但是大部分时间的话就是队列快一点的