洛谷 3385 【模板】负环

本文深入探讨了三种有效的负环检测方法:BFS_SPFA的两种变体和DFS_SPFA。通过实例对比,阐述了各方法的优劣及适用场景,强调了BFS_SPFA在稳定性上的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一道判断负环的模板题。

这里主要介绍三种判断负环的方法。

1.BFS_SPFA方法A

我们可以通过记录每个点的入队次数来判断负环是否存在,不难看出:一个点的入队次数一旦超过n次,则图中一定有负环存在。效率不高,不再提供代码。

2.BFS_SPFA方法B

我们可以记录每个点到源点最短路上经过了几个点,一旦超过n个,则负环一定存在,正确性比较显然、不再赘述。

这个方法为什么会比方法A效率高呢?举个例子:在一个由n个点构成的负环中,方法A要将此环遍历n次,而方法B遍历1次就行了.

3.DFS_SPFA方法

我们只需将SPFA改用DFS实现, 然后应用方法B, 我们就能高效的求出负环了. 

愉快的写完代码,提交完成,TLE. mengbi了一会下了组数据后发现,其实大多情况下该算法表现的十分优秀. 

DFS_SPFA

经过思考发现:如果一个图没有负环的话,以上方法就像一个弱化版本的SPFA,效率大大降低.所以该方法还是要慎用.

总结:

DFS_SPFA由于它的不稳定性,还是要慎用的.因为我们无法保证图中一定有负环(保证了还判什么).

BFS_SPFA的方法A就不考虑了, 方法B比他优秀的多....

总体来说个人认为求稳的话,还是使用BFS_SPFA比较保险.

AC代码:

这里给出了BFS方法B和DFS的方法:

 

#include <cstring>
#include <cstdio>
#include <cctype>
#include <queue>

inline void read(int & x)
{
    int k = 1; x = 0;
    char c = getchar();
    while (!isdigit(c))
        if (c == '-') c = getchar(), k = -1;
        else c = getchar();
    while (isdigit(c))
        x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
    x *= k;
}

int n, m, cnt, x, y, z, T, u, f[2020], d[2020], v[2020], k[2020], flag;

struct Edge { int v, w, nxt; } e[7000];

inline void addedge(int a, int b, int z) { ++cnt, e[cnt].nxt = f[a], e[cnt].v = b, e[cnt].w = z, f[a] = cnt; }

inline bool BFS_SPFA(void)
{
    memset(d, 0x3f, sizeof(d));
    memset(v, false, sizeof(v));
    d[1] = 0, v[1] = 1, k[1] = 0;
    std::queue<int> Q; Q.push(1);
    while (!Q.empty())
    {
        u = Q.front(), Q.pop(), v[u] = 0;
        for (int i = f[u]; i != -1; i = e[i].nxt)
            if (d[e[i].v] > d[u] + e[i].w)
            {
                d[e[i].v] = d[u] + e[i].w,
                k[e[i].v] = k[u] + 1;
                if (k[e[i].v] > n) return false;
                if (!v[e[i].v]) v[e[i].v] = 1, Q.push(e[i].v);
            }
    }
    return true;
}

void DFS_SPFA(int u)
{
    v[u] = true;
    for (int i = f[u]; i != -1; i = e[i].nxt)
        if (d[e[i].v] > d[u] + e[i].w)
        {
            d[e[i].v] = d[u] + e[i].w;
            if (v[e[i].v] == 1) { flag = 1; return; }
            DFS_SPFA(e[i].v);
        }
    v[u] = false;
}

inline void Work(void)
{
    flag = 0;
    memset(d, 0x3f, sizeof(d));
    memset(v, false, sizeof(v));
    d[1] = 0;
    DFS_SPFA(1);
    if (flag) puts("YE5");
    else puts("N0");
}

signed main()
{
    read(T);
    for (int plk = 1; plk <= T; ++plk)
    {
        memset(f, -1, sizeof(f)); cnt = 0;
        read(n), read(m);
        for (int i = 1; i <= m; ++i)
        {
            read(x), read(y), read(z);
            addedge(x, y, z);
            if (z >= 0) addedge(y, x, z);
        }
        if (!BFS_SPFA()) puts("YE5");
        else puts("N0");
        //Work();    
    }
    return 0;
}

 

转载于:https://www.cnblogs.com/yanyiming10243247/p/9900154.html

判断的经典算法是 Bellman-Ford 算法。该算法的主要思想是进行 n 次松弛操作,其中 n 为图中节点的数量。如果在第 n 次松弛操作后仍然存在可以被松弛的边,则说明图中存在。 在具体实现时,可以先将所有节点的距离初始化为正无穷大,起始节点的距离为 0。然后进行 n 次松弛操作,每次松弛操作都枚举所有边,如果该边的起点的距离加上边的权值小于终点的距离,则更新终点的距离。如果在第 n 次松弛操作后仍然存在可以被松弛的边,则说明图中存在。 具体代码实现可以参考以下代码: ```cpp #include <iostream> #include <vector> #include <queue> #include <cstring> using namespace std; const int INF = 0x3f3f3f3f; struct Edge { int to, weight; Edge(int _to, int _weight) : to(_to), weight(_weight) {} }; vector<Edge> edges[1000]; int dist[1000]; bool inQueue[1000]; bool bellmanFord(int start, int n) { memset(dist, INF, sizeof(dist)); dist[start] = 0; queue<int> q; for (int i = 1; i <= n; i++) { q.push(i); inQueue[i] = true; } while (!q.empty()) { int u = q.front(); q.pop(); inQueue[u] = false; for (int i = 0; i < edges[u].size(); i++) { int v = edges[u][i].to; int w = edges[u][i].weight; if (dist[u] != INF && dist[u] + w < dist[v]) { dist[v] = dist[u] + w; if (!inQueue[v]) { q.push(v); inQueue[v] = true; } if (dist[v] < 0) { return true; } } } } return false; } int main() { int n, m; cin >> n >> m; for (int i = 0; i < m; i++) { int u, v, w; cin >> u >> v >> w; edges[u].push_back(Edge(v, w)); } if (bellmanFord(1, n)) { cout << "YES" << endl; } else { cout << "NO" << endl; } return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值