spfa
99.9% 题 没有负环
2024.6.20
笔试题 都能SPFA一般,不是笔试题水,是笔试数据比较水!!!!,不是一锤子买卖 不担心超时TLE
BF的优化! 有点傻,遍历所有边更新,但是不一定要都更新啊!!! 优化!
只有当dist[a]变小,才更新
spfa巨快
很多正权图也能用SPFA过掉!!!
用BFS优化,用队列(用其他也行,推荐队列) 长得很像dijkstra算法II!!!
时间复杂度 O(m) <O(nm)
起点进queue
While 队列不空
1. t取队头; 弹出q.pop()队头
2.更新t的所有出边(从t指出来的边) 用已经更新过的 点的距离更新其他从这个点出来的边,我变成小了,从我出来的才会跟着一起变小!!
queue存待更新的点
// dijkstra II算法
#include<iostream>
#include<cstring>
#include<algorithm>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
// 这里改了 1e5
const int N = 100010;
int n, m;
// 邻接表
int h[N], w[N], e[N], ne[N], idx; // w[N]边的权重
int dist[N]; //dist 当前1号到i的最短距离
bool st[N]; // 是否已经确定dist[i]
void add(int a, int b, int c){
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
int spfa(){
memset(dist, 0x3f, sizeof dist); //最短路 初始化成无穷大
dist[1] = 0;
queue<int> q;
q.push(1);
st[1] = true; //存1号点在队列了
while(q.size()){
// queue非空 取队头=t
int t = q.front();
q.pop();
st[t] = false; // pop出来就要置为false
// 看所有t的出边的点的最短路能不能更新!!!
for(int i = h[t]; i != -1; i = ne[i]){
int j = e[i]; // j当前这个点,t出边的另外一头点
if(dist[j] > dist[t] + w[i]){
dist[j] = dist[t] + w[i];
if(!st[j]){
q.push(j);
st[j] = true;
}
}
}
}
return dist[n];
}
int main(){
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while(m --){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
int t = spfa();
// 上课的版本不行了,-1有bug!!!
if (t == 0x3f3f3f3f) puts("impossible");
else printf("%d\n", t);
return 0;
}
用spfa可以过dijkstra1 但是II不行,超时!!!!
spfa巨快,比dijkstra块!!!法二 spfa 会超时被卡, 换堆优化dijkstra
阴险的出题人,网格形状容易卡
只要物理健康的肉体还在和法律上 社会没死亡
可以随便是错没事的
身体不行,物理没了什么都没了!!!
spfa求负环!!
思路
-
Dist[x] 当前1到x的最短路
cnt[x] 当前最短路的边的数量????? -
每次更新
Dist[x] = dist[t] + w[i]
Cnt[x] = cnt[t] + 1;
从1-x的边数 = 1-t边数+1条边t-x
- 判负环
如果cnt[x] >=n 即边数>=n, 说明经过的点数>= n+1
但是一共n点,所以至少两点重复出现,即出现了环
又因为最短路,选择走环,说明走环让数减少,所以一定是个负环!!!
852 spfa求负环
无向图是特殊的有向图,可以用有向图的 模板
改了初始化dist不用
更新同时更新cnt
返回false true不是dist[n]
队列初始不是压入1, 因为不一定环是从1开始的,所有点都压到queue,确保每个点开始的环都被检测到!!!!
// spfa改的
#include<iostream>
#include<cstring>
#include<algorithm>
#include <queue>
using namespace std;
typedef pair<int, int> PII;
// 这里改了 1e5
const int N = 100010;
int n, m;
// 邻接表
int h[N], w[N], e[N], ne[N], idx; // w[N]边的权重
int dist[N], cnt[N];
bool st[N]; // 是否已经确定dist[i]
void add(int a, int b, int c){
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;
}
bool spfa(){
// memset(dist, 0x3f, sizeof dist); //最短路 初始化成无穷大
// dist[1] = 0;
// 不需要初始化,不记录距离,只判断负环
queue<int> q;
for(int i = 1; i <= n ; i ++){
// 确保每个点开始的环都能被检测到!!
q.push(i);
st[i] = true;
}
while(q.size()){
// queue非空 取队头=t
int t = q.front();
q.pop();
st[t] = false; // pop出来就要置为false
// 看所有t的出边的点的最短路能不能更新!!!
for(int i = h[t]; i != -1; i = ne[i]){
int j = e[i]; // j当前这个点,t出边的另外一头点
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]){
q.push(j);
st[j] = true;
}
}
}
}
return false;
}
int main(){
scanf("%d%d", &n, &m);
memset(h, -1, sizeof h);
while(m --){
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
if(spfa()) puts("Yes");
else puts("No");
return 0;
}
floyd
图论二 01:55:10
2024.6.21
这几个算法都要牢记时间复杂度!!!!
最短路的这种图,要在5min内写完,我的妈,至少十遍就熟了吧!!!
找工作不用算法竞赛,但是模板要背,
floyd超简单
用邻接矩阵存所有边
三重循环
原理:动态规划dp
d[k, I , j] : 从i到j, 只经过1-k个中间点的最短路= d[k-1, I, k] + d[k-1, k,j] 两段拼起来的 会后面细讲
k先循环,ij顺序可以颠倒
不是所有图论都画图的,思路重要
自环删去, 重边保留最短那条
妙啊,如果出现segmentation fault 用删代码法,一块块删到不报错,看哪里出问题!!!!
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 210, INF = 1e9; //why???
int n, m, Q;
int d[N][N]; //邻接矩阵
void floyd(){
for(int k = 1; k <=n; k ++)
for(int i = 1; i <=n; i ++)
for(int j = 1; j <=n; j ++)
d[i][j] = min(d[i][j], d[i][k] + d[k][j]); //dp背包
}
int main(){
scanf("%d%d%d", &n, &m, &Q);
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n ; j ++)
if(i == j) d[i][j] = 0;
else d[i][j] =INF;
while(m --){
int a, b, w;
scanf("%d%d%d", &a, &b, &w);
// 如果重边,最小
d[a][b] = min(d[a][b], w);
}
floyd();
while(Q --){
int a, b;
scanf("%d%d", &a, &b);
// if(d[a][b] == INF) puts("impossible");
// 因为如果没有通路,但是存在负权边,可能比无穷大小一点,所以/2
if(d[a][b] > INF / 2) puts("impossible");
else printf("%d\n", d[a][b]);
}
}
调代码:
printf输出法
TLE删代码法
就是经验, 思路+代码不停敲
总结:
算法都要记时间复杂度
因为题目数据范围给了很多提示
比如n=200 m很大
多半是floyd
N m都大十万
多半堆优化dijkstra或者spfa