主循环 for k from 1 to n: a. 在更新最短路之前,遍历所有满足 1 <= i < k 和 1 <= j < k 的顶点对 。计算 d[i][j] + g[i][k] + g[j][k],并用这个值更新 ans。 b. 执行标准 Floyd-Warshall 更新:for i from 1 to n, for j from 1 to n, 更新 d[i][j] = min(d[i][j], d[i][k] + d[k][j])。
关键点:为何更新 ans 的操作必须在更新 d 数组之前? 因为一旦执行了第 轮的 d[i][j] 更新,d[i][j] 的值就可能包含了经过顶点 的路径。此时再用 d[i][j] 计算环长,就违背了“路径 不得经过 ”的前提,可能会导致计算出的路径与边 发生重叠,而不是构成一个简单环。
cin >> n >> m; for (int i = 0; i < m; ++i) { int u, v, w; cin >> u >> v >> w; adj[u].push_back({v, w}); adj[v].push_back({u, w}); edges.emplace_back(u, v, w); }
ll ans = inf;
// 枚举每一条边作为环的一部分 for (constauto& edge : edges) { int u, v, w; tie(u, v, w) = edge; ll res = dij(u, v, u, v); if (res != inf) { ans = min(ans, res + w); } }
if (ans == inf) { cout << "No solution." << endl; } else { cout << ans << endl; }
return0; }
代码解释:
adj 是邻接表,用于 Dijkstra 算法。
edges 向量存储了所有的边信息 。我们需要遍历这个列表来枚举环的“接口边”。
主函数中,我们遍历 edges 里的每一条边 。
对于每条边,我们调用 dij(u, v, u, v)。这个函数计算在不使用边 (u, v) 的情况下,从 u 到 v 的最短路径。
dij 函数是标准的 Dijkstra 实现,但在松弛邻居时,加了一句 if ((u == ex && v == ey) || (u == ey && v == ex)) continue; 来实现逻辑上的边删除。
在以 s 为源点的 Dijkstra 过程中,我们计算 s 到其他所有顶点的最短距离 dist[]。假设在算法的某个时刻,我们正要从顶点 u 松弛其邻居 v,即处理边 u -> v。此时,我们已经有了 s 到 u 的最短路径长度 dist[u]。
如果此时我们发现一条从 u 指回源点 s 的边,即边 u -> s,权值为 w(u, s),那么我们就发现了一个环:。这个环的长度是 dist[u] + w(u, s)。
这个想法可以推广:在以 s 为源的Dijkstra中,对于任何一条边 u -> v,我们不仅知道 s 到 u 的最短路 dist[u],我们还能从其他地方(比如邻接矩阵)查到是否存在反向边 v -> s。如果存在,就形成了一个环 ,长度为 dist[u] + w(u, v) + w(v, s)。
一个更简洁的统一思路是:
对每个顶点 i 从 1 到 n: a. 以 i 为源点,运行一次完整的 Dijkstra 算法,计算出 i 到所有其他点的最短路 d[j]。 b. 在 Dijkstra 运行完毕后,遍历所有入边 (j, i)。对于每条这样的入边,d[j] + w(j, i) 就是一个候选环的长度。
在所有 n 次 Dijkstra 过程中找到的所有候选环中取最小值。
实际上,我们可以在 Dijkstra 内部完成这个过程:
ans = inf
对每个顶点 s 从 1 到 n: a. 以 s 为源点运行 Dijkstra。 b. 在 Dijkstra 的主循环中,当我们从优先队列中取出顶点 u 时,遍历 u 的所有出边 u -> v。 c. 此时,我们已经确定了 s 到 u 的最短路 d[u]。如果存在一条反向边 v -> s,那么 d[u] + w(u, v) + w(v, s) 就是一个环。 d. 但是这个方法不够普适。一个更简单且完全正确的做法是:在以 s 为源的Dijkstra中,对于从u扩展到v的边(u,v),如果v恰好是源点s,那么d[u] + w(u,s)就构成了一个环。这个环就是 。
所以,最终的算法是: 运行 N 次 Dijkstra。第 i 次以 i 为源点。在这次 Dijkstra 中,对于任意边 u -> v,d[u] + w(u,v) 是一个从 i 出发到 v 的路径长度。用它来更新 d[v]。同时,我们也检查是否存在边 v -> i。如果存在,则 d[v] + w(v,i) 形成一个环,用它来更新全局最小环。
我们甚至可以简化为:对每个点 s 做一次Dijkstra。对s的每个邻居v,w(s,v)加上v到s的最短路d(v,s)就是一个环。但d(v,s)需要另一次Dijkstra。所以最直接的还是运行N次Dijkstra。
cin >> n >> m; for (int i = 0; i < m; ++i) { int u, v, w; cin >> u >> v >> w; adj[u].push_back({v, w}); edges.emplace_back(u, v, w); } // 1. SPFA计算势能 if (!spfa()) { // 如果原图就有负环,那么最小环问题可能无定义(非简单环) // 具体取决于题目定义,这里假设我们只关心简单环 // 或者题目保证无负环 }
// 2. 重赋权 vector<vector<pair<int, ll>>> new_adj(n + 1); for (constauto& edge : edges) { int u, v, w; tie(u, v, w) = edge; new_adj[u].push_back({v, (ll)w + h[u] - h[v]}); }
// 3. 对每个点跑Dijkstra ll ans = inf; for (int i = 1; i <= n; ++i) { ll res = dij(i, new_adj); if(res != inf){ // 环权不变,直接使用重赋权后的环长 ans = min(ans, res); } }
if (ans == inf) { cout << "No solution." << endl; } else { cout << ans << endl; }
return0; }
代码解释:
spfa() 函数模拟了添加超级源点 的过程。将所有点的 h 初始化为0,等价于 到所有点距离为0,然后进行松弛。它计算了势能 h[]。
cin >> n >> m; for (int i = 0; i < m; ++i) { int u, v; cin >> u >> v; adj[u].push_back(v); adj[v].push_back(u); edges.push_back({u, v}); }
int ans = inf; for (auto& edge : edges) { int u = edge.first; int v = edge.second; int res = bfs(u, v, u, v); if (res != -1) { ans = min(ans, res + 1); } } if (ans == inf) { cout << "No solution." << endl; } else { cout << ans << endl; }
return0; }
复杂度分析:
时间复杂度: 。对 条边,每条边都执行一次 的BFS。
空间复杂度: 。
另一种思路: 运行 次BFS。以每个点 i 为源进行BFS。在BFS过程中,若从 u 扩展到 v 时发现 v 已被访问过,且 v 不是 u 的父节点,则发现一个环。环长为 d[u] + d[v] + 1。总复杂度为 ,在稀疏图上可能差于前者。
cin >> n >> m; double l = 1e9, r = -1e9; for (int i = 0; i < m; ++i) { int u, v, w; cin >> u >> v >> w; edges.emplace_back(u, v, w); l = min(l, (double)w); r = max(r, (double)w); } while (r - l > eps) { double mid = l + (r - l) / 2; if (check(mid)) { r = mid; // 存在均值<=mid的环,尝试更小的 } else { l = mid; // 不存在,答案肯定比mid大 } } cout << fixed << setprecision(8) << l << endl;
usingnamespace std; using ll = longlong; using pii = pair<ll, int>;
constint N = 3e5 + 5; const ll inf = 1e16;
int n, m; vector<pii> e[N], e2[N], e3[N]; int dfn[N], low[N], st[N], col[N], tp, tot, gn; bool vis[N], bj[N]; ll lw[N], fd[N], ans = inf; int ld[N], rd[N];
structnode { pii ma, mb; ll tg; } tr[N << 2];
voidtarjan(int u){ dfn[u] = low[u] = ++tot; st[++tp] = u; vis[u] = true; for (auto& edge : e[u]) { int v = edge.first; if (!dfn[v]) { tarjan(v); low[u] = min(low[u], low[v]); } elseif (vis[v]) { low[u] = min(low[u], dfn[v]); } } if (low[u] == dfn[u]) { int c = u; gn++; while (true) { vis[st[tp]] = false; col[st[tp]] = c; if (st[tp--] == u) break; } } }
voiddfs1(int u, int c){ vis[u] = true; for (auto& edge : e[u]) { int v = edge.first, w = edge.second; if (col[v] != c) continue; if (!vis[v]) { e2[u].push_back({v, w}); dfs1(v, c); } else { e3[u].push_back({v, w}); bj[u] = bj[v] = true; } } }
voiddfs2(int u){ if (bj[u]) ld[u] = ++tp, st[tp] = u; for (auto& edge : e2[u]) { int v = edge.first, w = edge.second; lw[v] = lw[u] + w; dfs2(v); } if (bj[u]) rd[u] = tp; }
voidbuild(int p, int l, int r){ tr[p].tg = inf; if (l == r) { int u = st[l]; tr[p].ma = {inf, u}; tr[p].mb = {lw[u], u}; return; } int mid = (l + r) / 2; build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r); psu(p); }
voidmdf(int p, int l, int r, int ql, int qr, ll w){ if (tr[p].mb.first == inf) return; if (l >= ql && r <= qr) { if (w < tr[p].tg) { tr[p].tg = w; pii val = {tr[p].mb.first + tr[p].tg, tr[p].mb.second}; tr[p].ma = min(tr[p].ma, val); } return; } int mid = (l + r) / 2; if (ql <= mid) mdf(p << 1, l, mid, ql, qr, w); if (qr > mid) mdf(p << 1 | 1, mid + 1, r, ql, qr, w); psu(p); }
voidban(int p, int l, int r, int k){ if (l == r) { tr[p].tg = tr[p].ma.first = tr[p].mb.first = inf; return; } int mid = (l + r) / 2; if (k <= mid) ban(p << 1, l, mid, k); elseban(p << 1 | 1, mid + 1, r, k); psu(p); }
voidrun_dij(int t, int s, int w0){ for (int i = 1; i <= tp; ++i) fd[st[i]] = inf; build(1, 1, tp); mdf(1, 1, tp, ld[s], ld[s], (ll)w0 - lw[s]);
while (true) { int u = tr[1].ma.second; fd[u] = tr[1].ma.first; if (u == t || fd[u] >= inf) break; ban(1, 1, tp, ld[u]); mdf(1, 1, tp, ld[u], rd[u], fd[u] - lw[u]); for (auto& edge : e3[u]) { int v = edge.first; ll w = edge.second; if (bj[v] && fd[v] == inf) { mdf(1, 1, tp, ld[v], ld[v], fd[u] + w - lw[v]); } } } if (fd[t] != inf) ans = min(ans, fd[t]); }
intmain(){ ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); cin >> n >> m; for (int i = 0; i < m; ++i) { int u, v, w; cin >> u >> v >> w; e[u].push_back({v, w}); }
for (int i = 1; i <= n; ++i) if (!dfn[i]) tarjan(i); for (int i = 1; i <= n; ++i) { if (col[i] != i || vis[i]) continue; dfs1(i, i); tp = 0; dfs2(i); if(tp == 0) continue;
for (int j = 1; j <= tp; ++j) { int u = st[j]; for (auto& edge : e3[u]) { int v = edge.first, w = edge.second; run_dij(u, v, w); } } } cout << (ans >= inf ? -1 : ans) << endl; return0; }