AtCoder Beginner Contest 308 Ex Make Q
这是官方题解的 \(O(n^3)\) 做法。
我们考虑枚举环上最特殊的那个点,也就是连出去一条边的点,设它为 \(S\)。
我们考虑使用 Dijkstra 以 \(O(n^2)\) 找到包含 \(S\) 的最小环。
具体地,使用 Dijkstra 建出最短路树 \(T\),设点 \(u\) 到 \(S\) 的最短路为 \(f_u\)。给每个点设一个 \(h_u\),若 \(u = S\) 则 \(h_u = S\),否则 \(h_u\) 为 \(S\) 的一个直接儿子,使得 \(u\) 在 \(h_u\) 子树内。这样,对于一条边 \((u, v, d)\),若 \(h_u \ne h_v\),那么经过这条边的最小环长度为 \(f_u + f_v + d\)。这样不仅能找出最小环长度,还能按顺序找到环上的每一个点。
设最小环长度为 \(k\),\(S\) 在最小环上相邻两点为 \(x, y\)。然后我们考虑 \(S\) 连向不在这个环上的点的边 \((S, v, d)\),对答案的贡献为 \(k + d\)。但是这还不够,我们还需要考虑 \(S\) 连出去的边 \((S, v, d)\) 在这个环上的情况。
注意到,若 \(v \ne x \land v \ne y\),这条边对最优解一定没有贡献。因为若我们强制钦定 \(v\) 不在环上出现,求出来的最小环长 \(t\) 一定 \(\ge k\),最终贡献到答案中的是 \(t + d\)。而我们显然是可以只保留环上 \(S \to v\) 的点,做到对答案的贡献 \(\le t + d\)(可自行画图)。
因此我们只用考虑边 \((S, x, d)\) 和边 \((S, y, d)\)。删除这两条边后,再做一遍最小环即可。
注意一些最小环不存在或图不连通的处理即可。
code
// Problem: Ex - Make Q
// Contest: AtCoder - AtCoder Beginner Contest 308
// URL: https://atcoder.jp/contests/abc308/tasks/abc308_h
// Memory Limit: 1024 MB
// Time Limit: 4000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
#define pb emplace_back
#define fst first
#define scd second
#define mems(a, x) memset((a), (x), sizeof(a))
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef long double ldb;
typedef pair<ll, ll> pii;
const int maxn = 310;
const int inf = 0x3f3f3f3f;
int n, m, a[maxn][maxn], f[maxn], g[maxn], h[maxn];
bool vis[maxn];
inline auto cycle(int S) {
mems(f, 0x3f);
mems(vis, 0);
mems(g, 0);
mems(h, 0);
f[S] = 0;
h[S] = S;
while (1) {
int mn = inf, p = -1;
for (int i = 1; i <= n; ++i) {
if (!vis[i] && f[i] < mn) {
mn = f[i];
p = i;
}
}
if (p == -1) {
break;
}
vis[p] = 1;
for (int i = 1; i <= n; ++i) {
if (f[i] > f[p] + a[p][i]) {
f[i] = f[p] + a[p][i];
g[i] = p;
h[i] = (p == S ? i : h[p]);
}
}
}
int mn = inf;
for (int i = 1; i <= n; ++i) {
for (int j = i + 1; j <= n; ++j) {
if (f[i] == inf || f[j] == inf || g[i] == j || g[j] == i || h[i] == h[j] || a[i][j] == inf) {
continue;
}
mn = min(mn, f[i] + f[j] + a[i][j]);
}
}
if (mn == inf) {
return make_pair(inf, vector<int>());
}
for (int i = 1; i <= n; ++i) {
for (int j = i + 1; j <= n; ++j) {
if (f[i] == inf || f[j] == inf || g[i] == j || g[j] == i || h[i] == h[j] || a[i][j] == inf) {
continue;
}
if (f[i] + f[j] + a[i][j] == mn) {
int x = i, y = j;
vector<int> vc;
while (x != S) {
vc.pb(x);
x = g[x];
}
vc.pb(S);
reverse(vc.begin(), vc.end());
while (y != S) {
vc.pb(y);
y = g[y];
}
return make_pair(mn, vc);
}
}
}
}
void solve() {
scanf("%d%d", &n, &m);
mems(a, 0x3f);
while (m--) {
int u, v, d;
scanf("%d%d%d", &u, &v, &d);
a[u][v] = a[v][u] = d;
}
int ans = inf;
for (int S = 1; S <= n; ++S) {
auto _ = cycle(S);
int d = _.fst;
auto vc = _.scd;
if (d == inf) {
continue;
}
mems(vis, 0);
for (int u : vc) {
vis[u] = 1;
}
for (int i = 1; i <= n; ++i) {
if (!vis[i]) {
ans = min(ans, d + a[S][i]);
}
}
int u = vc[1];
int t = a[S][u];
a[S][u] = a[u][S] = inf;
ans = min(ans, cycle(S).fst + t);
a[S][u] = a[u][S] = t;
u = vc.back();
t = a[S][u];
a[S][u] = a[u][S] = inf;
ans = min(ans, cycle(S).fst + t);
a[S][u] = a[u][S] = t;
}
printf("%d\n", ans == inf ? -1 : ans);
}
int main() {
int T = 1;
// scanf("%d", &T);
while (T--) {
solve();
}
return 0;
}