• 洛谷 题解 P3385 【【模板】负环】


    一、声明

    在下面的描述中,未说明的情况下,(N) 是顶点数,(M)是边数。

    二、判负环算法盘点

    想到判负环,我们会想到很多的判负环算法。例如:

    1. Bellman-Ford 判负环

    这个算法在众多算法中最为经典,复杂度 (O(N imes M))

    2. SPFA 判负环

    然而,这个算法是 Bellman-Ford 算法的队列优化版,这最短路方面卓有成效,但在判负环方面不见得有多少快。尽管在有负环的情况下会快很多,期望复杂度达到了 (O(K imes M))(K)是常数);但在没有负环的情况下,SPFA 算法会退化到 (O(N imes M))

    难道判负环的复杂度就由此停步于 (O(N imes M)) 之前吗?

    捂脸熊

    不,还有办法的!

    办法之一:SPFA之dfs版

    3. SPFA_dfs 判负环

    这个算法挺 科♂学 的,利用了 SPFA_dfs 可以迅速使大量节点得到更新,因此也更容易找到负环。然而,SPFA_dfs 死于不日前更新的毒瘤数据手里。

    不要着急,我们还有办法!

    4. 带卡界的 SPFA 算法

    我们想到,在有负环的情况下,SPFA 判负环的时间复杂度是期望 (O(M)) 的,非常的快。那么反过来,效率低下是否就代表没有负环?

    答案是肯定的!✔

    假设入队操作超过了 (T(N + M)) 次,那么就认为没有负环。((T) 一般取 (2)

    $large B!space U!space T!space $

    我们 WA 了!

    所以放弃,回去用SPFA_bfs版

    不!我们发现 11 个数据点只 WA 了 1 个点 (#9) ,还是比较不错的,所以我们想到增加 (T)

    我选择将数据下载了下来,在本地跑,经过二分,得出数据点#9的T最小是 (K = 20.076030space (eps=1^{-6})) (少 (0.000001) 都不行)

    然后就过了。

    超人熊

    有点不太保险????

    不过可以开大 (T) 啊!

    下表给出了几组 (T) 的值对应的情况:

    (T) 分值 时间消耗(ms) 对应评测记录id
    2 91 58 R16135858
    20.076030 100 198 R16135998
    30 100 270 R16136029
    100 100 754 R16136756
    300 100 2071 R16136864

    实际上耗时都不大。

    实际上运用建议开 (T = 2) (一般没人卡这种算法【注:卡的方法点击箭头了解】如果你真的怕被卡,(T) 开大点也没事毕竟最多1~2个TLE

    三、代码

    #include <queue>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    
    inline int readint()       
    {
        int flag = 1;
        char c = getchar();
        while ((c > '9' || c < '0') && c != '-') 
            c = getchar();
        if (c == '-') flag = -1, c = getchar();
        int init = c ^ '0';
        while ((c = getchar()) <= '9' && c >= '0') 
            init = (init << 3) + (init << 1) + (c ^ '0');
        return init * flag;
    }
    
    struct Edge {
        int v, w;
        int nxt;
        Edge() {}
        Edge(int _v, int _w, int _nxt) : v(_v), w(_w), nxt(_nxt) {}
    } edges[6007]; 
    // 链式前向星存图
    int top = 1;
    int n, m;
    
    int head[2007] = {0};
    int dis[2007] = {0};
    bool inqueue[2007] = {0};
    
    inline void add_edge(int u, int v, int w) // 单次加边操作
    {
        edges[top] = Edge(v, w, head[u]);
        head[u] = top++;
    }
    
    inline void add(int u, int v, int w) // 加边操作
    {
        add_edge(u, v, w);
        if (w >= 0) add_edge(v, u, w);
    }
    
    const double K = 20.076030; // 即题解中所说的 "T"
    bool SPFA_bfs()
    { 
        queue <int> q;
        q.push(1);
        inqueue[1] = 1;
        int times = 0; 
        while (!q.empty()) {
    		times++;
    		if (times > K * (n + m)) return 1;
    		// 以上两行:卡界
        	int n = q.front(); q.pop();
        	inqueue[n] = 0;
        	for (int i = head[n]; i != -1; i = edges[i].nxt) {
        		Edge &e = edges[i];
        		if (dis[e.v] > dis[n] + e.w) {
        			dis[e.v] = dis[n] + e.w;
        			if (!inqueue[e.v]) q.push(e.v);
        		}
        	}
        }
        return 0;
    }
    
    
    void van()
    {
        n = readint();
        m = readint();
        top = 1;
        memset(head, -1, sizeof(head));
        memset(dis, 0x3f, sizeof(dis));
        memset(inqueue, 0, sizeof(inqueue));
        dis[1] = 0;
        register int ui, vi, wi;
        for (register int i = 1; i <= m; i++) {
            ui = readint();
            vi = readint();
            wi = readint();
            add(ui, vi, wi);
        }
        if (SPFA_bfs()) puts("YE5");
        else puts("N0");
    }
    
    int main()
    {
        register int T = readint();
        while (T--) van();
        return 0;
    }
    
  • 相关阅读:
    网络配置
    yum源配置
    linux压缩命令
    linux下创建和删除软、硬链接
    linux挂载光盘
    Linux-chmod_命令的详细用法讲解
    linux_rpm命令
    Linux_Vi_命令
    anglarJs前端控制器的继承
    angularJs分层服务开发
  • 原文地址:https://www.cnblogs.com/hkxadpall/p/10357532.html
Copyright © 2020-2023  润新知