AtCoder Beginner Contest 308 Ex Make Q

洛谷传送门

AtCoder 传送门

这是官方题解的 \(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;
}
posted @ 2023-07-10 15:35  zltzlt  阅读(25)  评论(0编辑  收藏  举报