本文参考了如下题解,记录了自己的理解
数据结构
- dis[v]:1到v的最短距离(初值正无穷,不断更新)
- cnt[v]:1到v的最短路径的边数(到n退出,原因见下)
- vis[v]:标记v当前是否在队列中(避免有多条路径到v时,v重复入队)
原理
在 没有负环 的图中,任意两个节点之间的最短路径最多包含 n-1
条边。因为 n
个节点的路径最多有 n-1
条边,否则就会形成环,又由“最短路径”的加入条件可知此环为负环。比如有三个点,由1走到2走到3,此时已走过2条边,如果又能加入1(满足dis[3] + d(1,3) < dis[1]),则1与1的最短路径就包含了3条边,一定存在负环。
代码
核心:不断出队,遍历邻接点,判断最短路径,考虑是否加入新点
#include <bits/stdc++.h>
#define ll long long
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
using namespace std;
const int N = 2e3 + 5;
const ll INF = LLONG_MAX;
int t, n, m;
int cnt[N];
ll dis[N];
bool vis[N];
struct edge {
int v, w;
edge(int x, int y) {
v = x, w = y;
}
};
inline void judge(vector<edge> e[]) {
queue<int> q;
q.push(1);
dis[1] = 0, vis[1] = 1, cnt[1] = 0;
while (!q.empty()) {
int u = q.front();
q.pop();
vis[u] = 0;
for (auto& it : e[u]) {
int v = it.v, w = it.w;
if (dis[u] + w < dis[v]) {
dis[v] = dis[u] + w;
cnt[v] = cnt[u] + 1;
if (cnt[v] >= n) {
cout << "YES" << endl;
return;
}
if (!vis[v]) {
vis[v] = 1;
q.push(v);
}
}
}
}
cout << "NO" << endl;
}
int main()
{
IOS;
cin >> t;
while (t--) {
cin >> n >> m;
vector<edge> e[N];
memset(vis, 0, sizeof vis);
memset(cnt, 0, sizeof cnt);
fill(dis + 1, dis + 1 + n, INF);
for (int i = 1; i <= m; i++) {
int u, v, w;
cin >> u >> v >> w;
if (w >= 0)
{
e[u].push_back(edge(v, w));
e[v].push_back(edge(u, w));
}
else {
e[u].push_back(edge(v, w));
}
}
judge(e);
}
return 0;
}