gyro永不抽风

ああああああああああああああああおおおおおおおおおおおおおおおお

P3385 【模板】负环 - SPFA模板

关于SPFA

他死了。

SPFA原理

还是根据图论基本方程进行松弛操作,当无法继续松弛的时候,其实就已经达到了最优化,求出了单源最短路了。

为什么SPFA死了

因为数据很好卡。因为SPFA用的是双端队列,所以对于一些精心构造的图来说,复杂度其实是可以退化到$O(mn)$的。

为什么SPFA死了还要用

  • 因为可以求负环
  • 因为可以求带有负权边的最短路

SPFA和Dijkstra的区别

  • Dijkstra+heap是用小根堆,每次取出$\text {dis}$最小的点,来更新距离,那么这个点来说,最小距离就是当前的d。
  • SPFA是用双端队列,每次取出队头,来更新距离,它之后可能还会入队。它是一种动态逼近法,因为每次松弛距离都会减小,所以松弛一定会有结束的。如果一个点入队超过$n$次就是存在负环。
  • 如果是稠密图,Dijkstra + Heap比SPFA快。稀疏图则SPFA更快。SPFA可以有SLF和LLL两种优化,SLF就是$\text {dis}$比队头小就插入队头,否则插入队尾。
  • Dijkstra和Prim也很相似,它们的区别主要是d的含义,前者是到s的临时最短距离,后者是到树的临时最短距离,相同点是,每次找d最小的更新其它点的距离。

Ref: https://www.cnblogs.com/flipped/p/6830073.html

如何用SPFA判断负环

我们现在来思考一个问题:如果一张图上有$n$个点,那如果一条路径上有超过$n$个点会怎么样?

根据容斥原理,我们必定可以在路径上找到两个相同的点。换句话说,从一个点出发,又回到了这个点。然而整个过程确实求最短路的过程,路程$\text {dis}$不断在优化,所以这个环一定是负环(即路径和为负数)。而一条路径上有超过$n$个点,等价于一条路径有不少于$n$条边。而判断路径有多少条边其实非常容易,我们只需要在做松弛操作的时候更新记录路径条数的$\text {cnt}$数组就可以了:

题目

题目描述

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

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

输入格式

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

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

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

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

  • w0w \geq 0,则表示存在一条从 uuvv 边权为 ww 的边,还存在一条从 vvuu 边权为 ww 的边。
  • w<0w < 0,则只表示存在一条从 uuvv 边权为 ww 的边。

输出格式

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

输入输出样例

输入 #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

说明/提示

数据规模与约定

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

  • 1n2×1031 \leq n \leq 2 \times 10^31m3×1031 \leq m \leq 3 \times 10^3
  • 1u,vn1 \leq u, v \leq n104w104-10^4 \leq w \leq 10^4
  • 1T101 \leq T \leq 10

提示

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

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>
#include <algorithm>
#include <queue>
#include <cstring>

using namespace std;

const int maxm = 3005;
const int maxn = 2005;
int ecnt = 2;
int n, m;
int head[maxn], dis[maxn], cnt[maxn];
bool in[maxn];
struct edge { int to, next, w; } g[maxm << 1];
void add_edge(int u, int v, int w) { g[ecnt] = (edge) { v, head[u], w }; head[u] = ecnt ++; }
struct node { int dis, pos; };


bool spfa() {
for (int i = 1; i <= n; i ++)
dis[i] = 2e9, in[i] = false;
queue<node> q;
dis[1] = 0; q.push((node) { dis[1], 1 });
cnt[1] = 0; in[1] = true;
while (!q.empty()) {
int u = q.front().pos; q.pop();
in[u] = false;
for (int e = head[u]; e; e = g[e].next) {
int v = g[e].to;
if (dis[v] > dis[u] + g[e].w) {
dis[v] = dis[u] + g[e].w;
cnt[v] = cnt[u] + 1;
if (cnt[v] >= n) return true;
if (!in[v]) {
q.push((node) { dis[v], v });
in[v] = true;
}
}
}
}
return false;
}

int main() {
int T; cin >> T;
while (T --> 0) {
ecnt = 2;
memset(g, 0, sizeof(g));
memset(head, 0, sizeof(head));
cin >> n >> m;
for (int i = 1; i <= m; i ++) {
int u, v, w; cin >> u >> v >> w;
if (w >= 0) {
add_edge(u, v, w);
add_edge(v, u, w);
} else add_edge(u, v, w);
}
cout << (spfa() ? "YES" : "NO") << endl;
}
return 0;
}
__EOF__
-------------本文结束感谢您的阅读-------------

本文标题:P3385 【模板】负环 - SPFA模板

文章作者:gyro永不抽风

发布时间:2020年09月19日 - 23:09

最后更新:2020年09月19日 - 23:09

原始链接:http://gyrojeff.moe/2020/09/19/P3385-%E3%80%90%E6%A8%A1%E6%9D%BF%E3%80%91%E8%B4%9F%E7%8E%AF-SPFA%E6%A8%A1%E6%9D%BF/

许可协议: 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 转载请保留原文链接及作者!

真的不买杯奶茶吗?T^T

欢迎关注我的其它发布渠道