BZOJ3242 快餐店

原题传送门


题意

给定一个n条边n个点的连通图,求该图的某一点在该图距离最远的点距离它的距离的最小值。


题解

显然,答案是\(\frac {原图直径}{2}\)
本体的图有 \(n\) 个点 \(n\) 条边,很显然是基环树。
那么拆掉任意一条环上的边,该图就会变为一颗普通树。
随意选择一条环上的边断开,设一端为 \(s\),一端为 \(t\), 长度为 \(len\)
分类讨论一下该图的直径经过环和断边的情况:
1、直径经过断开的边;
2、直径不经过断开的边,经过环;
3、直径不经过环。
显然,若直径经过断开的边,就需要得出环上每个点的字数距离 \(s\) 最远点距离 \(s\) 的值,此值很显然可以在环上跑一边 \(DP\) 求出;
若直径不经过断开的边,则需要得出环上某两个点的子树的最远点之间的距离的最大值,此值仍然可以在环上跑一边 \(DP\) 求出。
故我们可以从 \(s\) 点开始,\(O(n)\) 从两个方向扫两遍环,在扫环的过程中,求出直径不经过环的情况的答案,同时维护两个 \(DP\) 值:
1、经过的环上的点的子树中,距离 \(s\) 最远的点距离 \(s\) 的值,定义该值为 \(u_1\)(正向)和 \(u_2\)(反向);
2、在经过的环上的点中,某两个距离最远的子树的最远点之间的距离,定义该值为 \(v_1\)(正向)和 \(v_2\)(反向);
扫描结束后,再遍历一遍环上的点更新答案值。

\(DP\) 方程:

\(ans = min(ans, max (max (v_1[i], v_2[i + 1]), u_1[i] + u_2[i + 1] + len))\)

注意更新结束后,还需要与直径不经过环的情况求一下最值。


代码:

#include 
#define int long long
using namespace std;
const int N = 1e5 + 5;
int n, sum, mx, cirsum, ans, ans1, tim, lstlen, f[N], u1[N], u2[N], v1[N], v2[N], fa[N], fal[N], tims[N], oncir[N], cirnum[N], cirlen[N];
vector  > v[N];
void make_cir (int s, int t) {
	int now = s;
	while (now != t) {
		oncir[now] = 1;
		cirnum[++ cirsum] = now;
		cirlen[cirsum] = fal[now];
		now = fa[now];
	}
	return ;
}
void dfs (int x) {
	tims[x] = ++ tim;
	for (int i = 0; i < v[x].size (); i ++) {
		int y = v[x][i].first;
		if (tims[y] && tims[y] > tims[x]) {
			make_cir (y, x);
			oncir[x] = 1;
			cirnum[++ cirsum] = x;
			cirlen[cirsum] = v[x][i].second;
		} else if (!tims[y]) {
			fa[y] = x;
			fal[y] = v[x][i].second;
			dfs (y);
		}
	}
	return ;
}
void dfs2 (int x, int lst) {
	for (int i = 0; i < v[x].size(); i ++) {
		int y = v[x][i].first, w = v[x][i].second;
		if (y == lst || oncir[y])
			continue ;
		dfs2 (y, x);
		ans = max (ans, f[x] + f[y] + w);
		f[x] = max (f[x], f[y] + w);
	}
	return ;
}
signed main () {
	scanf ("%lld", &n);
	for (int i = 1; i <= n; i ++) {
		int uu, vv, ww;
		scanf ("%lld%lld%lld", &uu, &vv, &ww);
		v[uu].push_back (make_pair (vv, ww));
		v[vv].push_back (make_pair (uu, ww));
	}
	dfs (1);
	for (int i = 1; i <= cirsum; i ++)
		dfs2 (cirnum[i], 0);
	for (int i = 1; i <= cirsum; i ++) {
		sum += cirlen[i - 1];
		u1[i] = max (u1[i - 1], f[cirnum[i]] + sum);
		v1[i] = max (v1[i - 1], f[cirnum[i]] + mx + sum);
		mx = max (mx, f[cirnum[i]] - sum);
	}
	lstlen = cirlen[cirsum];
	cirlen[cirsum] = 0;
	sum = 0;
	mx = 0;
	for (int i = cirsum; i >= 1; i --) {
		sum += cirlen[i];
		u2[i] = max (u2[i + 1], f[cirnum[i]] + sum);
		v2[i] = max (v2[i + 1], f[cirnum[i]] + mx + sum);
		mx = max (mx, f[cirnum[i]] - sum);
	}
	ans1 = v1[cirsum];
	for (int i = 1; i < cirsum; i ++)
		ans1 = min (ans1, max (max (v1[i], v2[i + 1]), u1[i] + u2[i + 1] + lstlen));
	ans = max (ans, ans1);
	printf ("%.1lf\n", (double)(ans / 2));
	return 0;
}

本博客思路参考自 https://blog.csdn.net/zlttttt/article/details/73149529

你可能感兴趣的:(BZOJ3242 快餐店)