模板---负环(学习笔记)
判断负环:DFS+SPFA BFS+SPFA
https://www.luogu.org/problemnew/show/P3385
原理:DFS判断负环:从某一个点开始DFS又回到了这个点.BFS判断负环:某一个点进出队列超过n次
判断负环这个东西比较玄学,有的题目卡DFS,有的题目卡BFS.
DFS+SPFA
#include<bits/stdc++.h>
using namespace std;
int T,n,m,a,b,c,num_edge;
bool flag,bj[20005];
int head[20005],w[20005];
struct Edge{
int to,nxt,dis;
}edge[100005];//结构体存边
void add_edge(int x,int y,int z){
edge[++num_edge].nxt=head[x];
edge[num_edge].to=y;
edge[num_edge].dis=z;
head[x]=num_edge;
}//链式前向星存图
void spfa(int x){
if(flag==true) return; //优化
bj[x]=true; //标记x点被DFS过
for(int i=head[x];i;i=edge[i].nxt){//枚举与该点相邻的每一条边
int y=edge[i].to; //这条边连接的点
int z=edge[i].dis; //这条边的权值
if(w[y]>w[x]+z){ //松弛操作
w[y]=w[x]+z;
if(bj[y]==true){ //如果这个点之前走过,说明存在环
flag=true;
return;
}
spfa(y); //否则继续DFS下去
}
}
bj[x]=false; //回溯
return;
}
int main(){
scanf("%d",&T);
while(T--){ //T组数据
flag=false;
memset(w,0,sizeof(w));
memset(bj,0,sizeof(bj));
memset(head,0,sizeof(head));
memset(edge,0,sizeof(edge));
//多组数据一定要注意重置初始化
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&a,&b,&c);
add_edge(a,b,c);
if(c>=0) add_edge(b,a,c);
//根据题目要求,如果这条边边的权值大于0,建双向边
}
for(int i=1;i<=n;i++){
spfa(i);
if(flag==true) break;
//找到负环,做个标记,直接退出循环
}
if(flag==true) printf("YE5\n");
else printf("N0\n");
//这道题的输入真的有毒,你发现了吗?
}
return 0;
}
BFS+SPFA
#include<bits/stdc++.h>
#define N 100005
using namespace std;
int n,m,T;
bool vis[N];
int head[N],tot,dis[N],cnt[N],q[N];
struct node{int nxt,to,w;}edge[N<<1];
//因为有双向边,所以数组长乘2
void add(int x,int y,int z){
edge[++tot].nxt=head[x];
edge[tot].to=y;
edge[tot].w=z;
head[x]=tot;
}
inline bool spfa(int s){
int l=0,r=0; //队列的头尾指针
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
memset(cnt,0,sizeof(cnt));
memset(q,0,sizeof(q)); //多组数据重置
vis[s]=true;cnt[s]=1;dis[s]=0; q[r++]=s;
//队列初始化,vis标记是否在队列里;
//cnt记录该点进出队列的次数,dis距离,q模拟队列;
while(l!=r){
int u=q[l++]; //出队
if(l>n) l=0; //重复使用指针l,r,可理解为循环队列,下文同理
vis[u]=false;//标记为不在队列里
for(int i=head[u];i;i=edge[i].nxt){
if(dis[edge[i].to]>dis[u]+edge[i].w){ //松弛操作
dis[edge[i].to]=dis[u]+edge[i].w;
cnt[edge[i].to]=cnt[u]+1;//记录入队次数
if(cnt[edge[i].to]>=n&&edge[i].w<0)
return true;
//判断负环:一个点进出队列的次数超过n次,且权值为负
if(!vis[edge[i].to]){
vis[edge[i].to]=true;//不在队列就入队
if(dis[edge[i].to]>dis[q[l]]){
l--;
if(l<0) l=n;
q[l]=edge[i].to;
}
else{
q[r++]=edge[i].to;
if(r>n) r=0;
}
}
}
}
}
return false;
}
int main(){
scanf("%d",&T);
while(T--){
memset(head,0,sizeof(head));
scanf("%d%d",&n,&m);
for(int i=1,u,v,w;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);
if(w>=0) add(v,u,w);
}
if(spfa(1)) printf("YE5\n");
else printf("N0\n");
}
}