Luogu P3385 【模板】负环 - 题解

【模板】负环

题目描述

给定一个 n n n 个点的有向图,请求出图中是否存在从顶点 1 1 1 出发能到达的负环。

负环的定义是:一条边权之和为负数的回路。

输入格式

本题单测试点有多组测试数据

输入的第一行是一个整数 T T T,表示测试数据的组数。对于每组数据的格式如下:

第一行有两个整数,分别表示图的点数 n n n 和接下来给出边信息的条数 m m m

接下来 m m m 行,每行三个整数 u , v , w u, v, w u,v,w

  • w ≥ 0 w \geq 0 w0,则表示存在一条从 u u u v v v 边权为 w w w 的边,还存在一条从 v v v u u u 边权为 w w w 的边。
  • w < 0 w < 0 w<0,则只表示存在一条从 u u u v v v 边权为 w w w 的边。

输出格式

对于每组数据,输出一行一个字符串,若所求负环存在,则输出 YES,否则输出 NO

样例 #1

样例输入 #1

2
3 4
1 2 2
1 3 4
2 3 1
3 1 -3
3 3
1 2 3
2 3 4
3 1 -8

样例输出 #1

NO
YES

提示

数据规模与约定

对于全部的测试点,保证:

  • 1 ≤ n ≤ 2 × 1 0 3 1 \leq n \leq 2 \times 10^3 1n2×103 1 ≤ m ≤ 3 × 1 0 3 1 \leq m \leq 3 \times 10^3 1m3×103
  • 1 ≤ u , v ≤ n 1 \leq u, v \leq n 1u,vn − 1 0 4 ≤ w ≤ 1 0 4 -10^4 \leq w \leq 10^4 104w104
  • 1 ≤ T ≤ 10 1 \leq T \leq 10 1T10
提示

请注意, m m m 不是图的边数。

题解

题意

传送门:link

给你 T T T 个图,每个图有 n n n 个点和 m m m 条由 u u u v v v 边权为 w w w 的边。

现给你这 m m m 条边,求图中是否有负环。

做法

算法分析

根据题的大意我们可以看出,这是一道单源最短路的题目。

单源最短路算法有 dij 和 SPFA,那这道题应该用哪道算法呢?

我们看一下数据范围:

  • 1 ≤ n ≤ 2 × 1 0 3 1 \leq n \leq 2 \times 10^3 1n2×103 1 ≤ m ≤ 3 × 1 0 3 1 \leq m \leq 3 \times 10^3 1m3×103

  • 1 ≤ u , v ≤ n 1 \leq u, v \leq n 1u,vn − 1 0 4 ≤ w ≤ 1 0 4 -10^4 \leq w \leq 10^4 104w104

  • 1 ≤ T ≤ 10 1 \leq T \leq 10 1T10

很明显会有负环,这时 dij 就行不通了,我们就需要用 SPFA 了。

算法讲解

SPFA 为能够有效判断负环的一种单源最短路算法,但其算法复杂度比不上 dij,在存在菊花图的情况下,很容易原地爆炸。

但由于能够判断负环的性质,才使得它在同类最短路算法中没有被淘汰(可应用于 Johnson 全源最短路算法中),其主要思想为贪心

SPFA 的独特性质在于他的一个点能够不止一次进队,这个特点有利也有弊,

利在于它可以通过入队次数来判断负环;

弊在于它容易被卡(关于SPFA,他死了)。

但在此题中它能完美胜任 dij(他不能判负环)。

算法实现

答题思路很简单,先链式前向星存图,再用 SPFA 遍历即可,所以问题在于 SPFA 的整体框架。

收我们需要一个队列,先把 1 1 1 号点存进去,再遍历到与 1 1 1 号点所连接着的点,依次松弛、入队,如果一个点入队次数大于等于 n n n 就直接输出 YES 接着 return;如果队列为空了,但依旧没有输出 YES 就输出 NO

Code

源代码 - C++

//注释极少版
#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>

using namespace std;
int t,cnt;
int dis[10001],v[10001];
int head[10001],tim[10001];

struct Node{
    int to,next,dis;
}edge[100001];

void add(int t1,int t2,int t3) {//存图
    cnt++;
    edge[cnt].to=t2;
    edge[cnt].dis=t3;
    edge[cnt].next=head[t1];
    head[t1]=cnt;
}

void SPFA_dui(int n) {
    queue<int> q;
    memset(dis,0x3f,sizeof(dis));
    memset(v,0,sizeof(v));
    memset(tim,0,sizeof(tim));
    //初始化
    dis[1]=0;
    v[1]=1;
    q.push(1);//加入 1
    while (!q.empty()) {
        int x=q.front();
        q.pop();
        v[x]=0;
        for (int i=head[x];i;i=edge[i].next) {//链式前向星遍历
            int y=edge[i].to;
            if (dis[y]>dis[x]+edge[i].dis) {//relax
                dis[y]=dis[x]+edge[i].dis;
                tim[y]=tim[x]+1;
                if (tim[y]>=n) {//有负环
                    cout<<"YES"<<endl;
                    return;
                }
                if (!v[y]) {//入队
                    v[y]=1;
                    q.push(y);
                }
            }
        }
    }
    cout<<"NO"<<endl;

}

int main() {
    scanf("%d",&t);
    for (int i=1;i<=t;i++) {
        memset(head,0,sizeof(head));//有多组数据,记得清空
        int n,m;
        scanf("%d%d",&n,&m);
        for (int j=1;j<=m;j++) {
            int t1,t2,t3;
            cin>>t1>>t2>>t3;
            add(t1,t2,t3);
            if (t3>=0) {
                add(t2,t1,t3);
            }
        }
        SPFA_dui(n);
        cnt=0;
    }
}

广告 \Huge{\bold{广告}} 广告

绿树公司官方博客 \Huge{\bold{绿树公司官方博客}} 绿树公司官方博客

l v s h u . w s s . c c \Huge{\bold{lvshu.wss.cc}} lvshu.wss.cc

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lvshu · 绿树

非常感谢您的搭讪

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值