SPFA两个作用
- 求最短路径
- 判断是否存在负环
原理:从起点开始加入队列,队首更新与其相邻的点,如果可以松弛就松弛,松弛之后如果这个点不在队列中,加入队列 如果一个点入队次数超过n,存在负环
求最短路
模板如下
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
const int N=1e5+10,M=5e5+10,INF=0x3f3f3f3f;
int n,m,s;//n个节点,m条边,s为源点
bool vis[N];//判断是否在队列中,防止一个队列出现重复点
int dis[N];//点i到源点最短距离
struct node {//链式前向星
int to,next,w;
}edge[M];
int cnt,head[N];
void init() {
cnt=0;
memset(head,-1,sizeof(head));
for(int i=1; i<=n; i++) {
vis[i]=0;
dis[i]=INF;
}
}
void addEdge(int from,int to,int w) { // 起点,终点,边长
edge[cnt].to=to; // 该边的终点
edge[cnt].w=w; // 权值
edge[cnt].next=head[from]; // (指向head[from]后,head[from]又指向了自己)
head[from]=cnt++; // 以from结点为起点的边在edge数组中存储的下标
}
void SPFA(int s) {//s为源点
queue<int>Q;
Q.push(s);// 源点进队
vis[s]=1;
dis[s]=0;
while(!Q.empty()) {
int start=Q.front();
Q.pop();
vis[start]=0; // 在SPFA中这儿需要改为0,因为每个节点需要重复进队
for(int i=head[start]; ~i; i=edge[i].next) { //取出start结点在edge中的起始存储下标(当i=0,即edge[i].next为0,说明以start节点为起始点的边全部访问完)
int w=edge[i].w;
int end=edge[i].to;
if(dis[end]>dis[start]+w) { // 松弛操作
dis[end]=dis[start]+w;
if(!vis[end]) { // 末端点不在队列
Q.push(end); //加入队列
vis[end]=1;
}
}
}
}
}
模板题如下
https://www.acwing.com/problem/content/description/853/
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
const int N=1e5+10,M=5e5+10,INF=0x3f3f3f3f;
int n,m,s;//n个节点,m条边,s为源点
bool vis[N];//判断是否在队列中,防止一个队列出现重复点
int dis[N];//点i到源点最短距离
struct node {//链式前向星
int to,next,w;
}edge[M];
int cnt,head[N];
void init() {
cnt=0;
memset(head,-1,sizeof(head));
for(int i=1; i<=n; i++) {
vis[i]=0;
dis[i]=INF;
}
}
void addEdge(int from,int to,int w) { // 起点,终点,边长
edge[cnt].to=to; // 该边的终点
edge[cnt].w=w; // 权值
edge[cnt].next=head[from]; // (指向head[from]后,head[from]又指向了自己)
head[from]=cnt++; // 以from结点为起点的边在edge数组中存储的下标
}
void SPFA(int s) {//s为源点
queue<int>Q;
Q.push(s);// 源点进队
vis[s]=1;
dis[s]=0;
while(!Q.empty()) {
int start=Q.front();
Q.pop();
vis[start]=0; // 在SPFA中这儿需要改为0,因为每个节点需要重复进队
for(int i=head[start]; ~i; i=edge[i].next) { //取出start结点在edge中的起始存储下标(当i=0,即edge[i].next为0,说明以start节点为起始点的边全部访问完)
int w=edge[i].w;
int end=edge[i].to;
if(dis[end]>dis[start]+w) { // 松弛操作
dis[end]=dis[start]+w;
if(!vis[end]) { // 末端点不在队列
Q.push(end); //加入队列
vis[end]=1;
}
}
}
}
}
int main() {
while(scanf(" %d%d",&n,&m)!=EOF) {
init();
for(int i=1; i<=m; ++i) { // 输入m条边
int from,to,w; // 起点 终点 权值
scanf(" %d%d%d",&from,&to,&w);
addEdge(from,to,w); //若是无向图,反过来再加一次
}
SPFA(1);
if(dis[n]!=INF)
printf("%d\n",dis[n]);
else
printf("impossible\n");
}
return 0;
}
判断负环
很简单,开个cir数组记录加入队列的次数,spfa变成bool型函数。
涉及push的时候cir[i]++,cir[i]>=n就return 1,说明存在负环
模板
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
const int N=1e5+10,M=5e5+10,INF=0x3f3f3f3f;
int n,m,s;//n个节点,m条边,s为源点
bool vis[N];//判断是否在队列中,防止一个队列出现重复点
int dis[N];//点i到源点最短距离
struct node {//链式前向星
int to,next,w;
} edge[M];
int cnt,head[N];
int cir[N];//如果cir[i]>=n,存在负环
void init() {
cnt=0;
memset(head,-1,sizeof(head));
for(int i=1; i<=n; i++) {
vis[i]=0;
dis[i]=INF;
cir[i]=0;
}
}
void addEdge(int from,int to,int w) { // 起点,终点,边长
edge[cnt].to=to; // 该边的终点
edge[cnt].w=w; // 权值
edge[cnt].next=head[from]; // (指向head[from]后,head[from]又指向了自己)
head[from]=cnt++; // 以from结点为起点的边在edge数组中存储的下标
}
bool SPFA(int s) {//s为源点
queue<int>Q;
Q.push(s);// 源点进队
cir[s]++;
vis[s]=1;
dis[s]=0;
while(!Q.empty()) {
int start=Q.front();
Q.pop();
vis[start]=0; // 在SPFA中这儿需要改为0,因为每个节点需要重复进队
for(int i=head[start]; ~i; i=edge[i].next) { //取出start结点在edge中的起始存储下标(当i=0,即edge[i].next为0,说明以start节点为起始点的边全部访问完)
int w=edge[i].w;
int end=edge[i].to;
if(dis[end]>dis[start]+w) { // 松弛操作
dis[end]=dis[start]+w;
if(!vis[end]) { // 末端点不在队列
Q.push(end); //加入队列
vis[end]=1;
cir[end]++;
if(cir[end]>=n) return true;//存在负环
}
}
}
}
return false;
}
和上面那个就几行不一样
例题
http://poj.org/problem?id=3259
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
const int N=1e5+10,M=5e5+10,INF=0x3f3f3f3f;
int n,m,s;//n个节点,m条边,s为源点
bool vis[N];//判断是否在队列中,防止一个队列出现重复点
int dis[N];//点i到源点最短距离
struct node {//链式前向星
int to,next,w;
} edge[M];
int cnt,head[N];
int cir[N];//如果cir[i]>=n,存在负环
void init() {
cnt=0;
memset(head,-1,sizeof(head));
for(int i=1; i<=n; i++) {
vis[i]=0;
dis[i]=INF;
cir[i]=0;
}
}
void addEdge(int from,int to,int w) { // 起点,终点,边长
edge[cnt].to=to; // 该边的终点
edge[cnt].w=w; // 权值
edge[cnt].next=head[from]; // (指向head[from]后,head[from]又指向了自己)
head[from]=cnt++; // 以from结点为起点的边在edge数组中存储的下标
}
bool SPFA(int s) {//s为源点
queue<int>Q;
Q.push(s);// 源点进队
cir[s]++;
vis[s]=1;
dis[s]=0;
while(!Q.empty()) {
int start=Q.front();
Q.pop();
vis[start]=0; // 在SPFA中这儿需要改为0,因为每个节点需要重复进队
for(int i=head[start]; ~i; i=edge[i].next) { //取出start结点在edge中的起始存储下标(当i=0,即edge[i].next为0,说明以start节点为起始点的边全部访问完)
int w=edge[i].w;
int end=edge[i].to;
if(dis[end]>dis[start]+w) { // 松弛操作
dis[end]=dis[start]+w;
if(!vis[end]) { // 末端点不在队列
Q.push(end); //加入队列
vis[end]=1;
cir[end]++;
if(cir[end]>=n) return true;//存在负环
}
}
}
}
return false;
}
int main() {
int F;
scanf("%d",&F);
while(F--) {
int q;
scanf(" %d%d%d",&n,&m,&q);
init();//初始化涉及参数n,所以必须放在输入n之后!!!!
for(int i=1; i<=m; i++) {
int from,to,w;
scanf(" %d%d%d",&from,&to,&w);
addEdge(from,to,w);
addEdge(to,from,w);
}
for(int i=1; i<=q; i++) {
int from,to,w;
scanf(" %d%d%d",&from,&to,&w);
w*=-1;
addEdge(from,to,w);
}
bool t=SPFA(1);
if(t) printf("YES\n");
else printf("NO\n");
}
return 0;
}