洛谷 题解 P3385 【【模板】负环】
一、声明
在下面的描述中,未说明的情况下,\(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\) 开大点也没事~~毕竟最多12个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;
}