P3385 【模板】负环 - SPFA模板

新博客地址:https://gyrojeff.top,欢迎访问! 本文为博客自动同步文章,为了更好的阅读体验,建议您移步至我的博客👇

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

文章作者:gyro永不抽风

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

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

原始链接:http://hexo.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) 转载请保留原文链接及作者!

关于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}$数组就可以了:

$$ \text {dis}[u] + g[e].w < \text {dis}[v] \Rightarrow \text {cnt} [v] = \text {cnt} [u] + 1 $$

题目

题目描述

给定一个 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;
}
posted @ 2020-09-20 10:48  gyro永不抽风  阅读(148)  评论(0编辑  收藏  举报