模板---负环(学习笔记)

判断负环: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");
    }
}

posted on 2018-10-27 15:43  PPXppx  阅读(266)  评论(0编辑  收藏  举报