1. 程式人生 > >洛谷 題解 P3385 【【模板】負環】

洛谷 題解 P3385 【【模板】負環】

int() tps 放棄 ini getch memset cor 卓有成效 bellman

一、聲明

在下面的描述中,未說明的情況下,\(N\) 是頂點數,\(M\)是邊數。

二、判負環算法盤點

想到判負環,我們會想到很多的判負環算法。例如:

1. Bellman-Ford 判負環

這個算法在眾多算法中最為經典,復雜度 \(O(N\times M)\)

2. SPFA 判負環

然而,這個算法是 Bellman-Ford 算法的隊列優化版,這最短路方面卓有成效,但在判負環方面不見得有多少快。盡管在有負環的情況下會快很多,期望復雜度達到了 \(O(K\times M)\)\(K\)是常數);但在沒有負環的情況下,SPFA 算法會退化到 \(O(N\times M)\)

難道判負環的復雜度就由此停步於 \(O(N\times 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.076030\space (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;
}

洛谷 題解 P3385 【【模板】負環】