【bzoj 2180】最小直径生成树(图论)

最小直径生成树

题目链接:bzoj 2180

题目大意

给你一个无向连通图,然后要你找到一棵生成树使得它的直径最小。
输出直径大小。

思路

首先,我们有直径,又会有生成树。
那我们不难想到一个叫做重心的东西。

但是重心不一定在点上,有一个绝对重心的东西,它就是可能会在边上的。
那不难想到一定会有至少两个点到绝对重心的距离是最长的,那这两个点之间就是我们要的直径。

那接着就是如何找到这个绝对重心,以及怎么算这个位置。
那我们首先要预处理一下东西:Floyed 求多源最短路,排序得出一个点到其它点距离从小到大的顺序。

那我们接着考虑枚举边,然后你考虑如果绝对重心在这条边上,你要怎么找它的位置,以及如何算距离。
那算距离好说,如果你知道了那两个点,你可以用这两个点分别到边的两个点的记录加上边的长度得到。这样子弄的好处就是你可以不用确切算出绝对重心的位置。
那接着就是如何找这两个点了。

那你考虑枚举其中一个点,从而找另一个对于最优的点,那这个直接搞是不行的,所以我们要想办法通过一个特点的顺序找到一个特定的寻找方法,从而得到优化。
我们考虑按你枚举的点到别的点的远近来枚举,然后每个位置的点到另一个点的距离会长成一条折线的样子,那我们需要的就是找那些折线折的位置(那里最高),只要在哪些地方算一下即可。

那就简单了,只要枚举后面反而比前面最大的大了就说明到了一个新的位置。(然后注意一下你是要到前面的点的距离从大到小枚举)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define INF 0x3f3f3f3f3f3f3f3f

using namespace std;

ll n, m, x, y, z;
ll f[201][201], a[201][201];
ll rnk[201][201], tmp;
ll ans;

int main() {
	scanf("%lld %lld", &n, &m);
	
	tmp = INF;
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			f[i][j] = a[i][j] = INF;
	
	for (ll i = 1; i <= n; i++) f[i][i] = a[i][i] = 0;
	for (ll i = 1; i <= m; i++) {
		scanf("%lld %lld %lld", &x, &y, &z);
		if (f[x][y] < z) continue;
		f[x][y] = f[y][x] = z;
		a[x][y] = a[y][x] = z;
	}
	
	for (ll k = 1; k <= n; k++)//Floyed 跑最短路
		for (ll i = 1; i <= n; i++)
			for (ll j = 1; j <= n; j++)
				if (f[i][j] > f[i][k] + f[k][j])
					f[i][j] = f[i][k] + f[k][j];
	

	for (ll i = 1; i <= n; i++) {
		for (ll j = 1; j <= n; j++) rnk[i][j] = j;
		for (ll j = 1; j < n; j++)//直接冒泡给到每个点距离排序
			for (ll k = j + 1; k <= n; k++) {
				if (f[i][rnk[i][j]] > f[i][rnk[i][k]])
					swap(rnk[i][j], rnk[i][k]);
			}
	}
	
	ans = INF;
	for (ll i = 1; i <= n; i++)//直接枚举边
		for (ll j = 1; j <= n; j++) {
			if (a[i][j] == tmp || f[i][j] == 0) continue;
			ll now = n;
			for (ll k = n - 1; k >= 2; k--) {//倒序
				if (f[i][rnk[j][k]] > f[i][rnk[j][now]]) {//每找到一个峰点都算一次
					ans = min(ans, f[j][rnk[j][k]] + f[i][rnk[j][now]] + a[i][j]);
					now = k;
				}
			}
		}
	printf("%lld", ans);
	
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值