HDU 4081 Qin Shi Huang's National Road System

传送门

“在必须要一条非mst边的情况下,怎么快速组织新的mst?”

(HDU 4081,HDU 4126

无向图、完全图(给你每个点的坐标),A是某条边上两端点的点权之和,B是在上述两点已经连通的情况下图的最小生成树(只用再找n-2条边了)的值。想让A/B最大。

这个题也是类似求比值最优的问题,没法简单贪心求解,只能枚举、而且是全局求解,不能仅限在mst中。
(当然,最简单的方法就是每次枚举都重新算一遍mst。。然而可以找到一些性质,只用算一次mst就够了。)

枚举每条边,也就枚举了每种可能的A,有两种情况:(借助kruskal算法理解)

  • 该边 是 mst上的边,在这两点已经确定的情况下,B的最小值就是mst的值减去该边的权值。
  • 该边 不是 mst上的边,此时mst加上该边一定会有且仅有一个环,环上除此边以外任意一条边的权值都小于等于该边的权值,而且环上去除任意一条边之后就是生成树。在这种情况下,B的最小值就是mst的值减去该环上mst边中的最大边。(环上找第二大)

为什么只用在这个环上做文章?为什么该环以外的其他mst边都不用变?
反证法。若其他mst边还有更好的选择,那原始mst为什么不选?

可以设这条不在mst中的边的起点和终点为i,j,那么只要求出在mst中ij路径上(有且只有一条路径)的最大边权max_edge[i][j]就可以了(求“次小生成树”也会用到这个)。用prim可以顺便求出来这个。

所以这个prim要额外完成两件事,一个是确定任意两点间的max_edge[i][j];另一个是区分某条边是否属于mst。

稍微多说几句关于求这个max_edge[i][j]
每次prim循环得到当前u以后,将u视为端点,将之前加入的点视为另一端,进行求解,可以想到这样是完备的。然后,还要记录每个点的pre,当前u仅连接到pre[u], 所以实质是利用max_edge[pre[u]][j](已经计算过)来更新传递max_edge[u][j]的。

#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
#include <cmath>
#include <queue>
using namespace std;

const double INF = 1e9;                // double
const int MAXN = 1000;
int T, N;

struct Point
{
	int x, y, w;
}city[MAXN];

double g[MAXN][MAXN];
bool vis[MAXN];
double d[MAXN];

bool in_mst[MAXN][MAXN];
double max_edge[MAXN][MAXN];          // 
int pre[MAXN];                        // 这俩不用初始化,因为可以证明用到的都已在本轮次计算过
double mst;
double ans;

struct Node
{
	int n;
	double d;
	bool operator<(const Node& n0) const
	{
		return d > n0.d;
	}
};

void init()
{
	//fill(vis, vis + N, 0);          再也不用fill,忘了HDU 2844的教训?
	//fill(d, d + N, INF);
	for (int i = 0; i < N; i++)
	{
		vis[i] = false;
		d[i] = INF;
	}
	memset(in_mst, 0, sizeof in_mst);
	mst = 0.0;
	ans = -1.0;
}

double getDistance(int i, int j)
{
	return sqrt(pow(city[i].x - city[j].x, 2) + pow(city[i].y - city[j].y, 2));    // pow有泛型定义,但不知出自哪个头文件
}

void prim()
{
	priority_queue<Node> q;
	d[0] = 0.0;
	q.push(Node{ 0,d[0] });
	for (; !q.empty();)
	{
		Node u = q.top();
		q.pop();
		if (vis[u.n]) continue;

		mst += u.d;
		vis[u.n] = true;
		if (u.n != 0)
			in_mst[u.n][pre[u.n]] = in_mst[pre[u.n]][u.n] = true;     //

		for (int j = 0; j < N; j++)
		{
			if (vis[j] && j != u.n)                                   // u.n现在只能跟着pre[u.n]混
			{
				if (j == pre[u.n]) max_edge[u.n][j] = max_edge[j][u.n] = g[u.n][j];
				else max_edge[u.n][j] = max_edge[j][u.n] = max(max_edge[pre[u.n]][j], g[u.n][pre[u.n]]);
			}
			else if (!vis[j] && g[u.n][j] < d[j])
			{
				d[j] = g[u.n][j];
				pre[j] = u.n;                                         //
				q.push(Node{ j,d[j] });
			}
		}
	}
}

int main()
{
	scanf("%d", &T);
	for (; T--;)
	{
		scanf("%d", &N);
		init();
		for (int i = 0; i < N; i++)
			scanf("%d%d%d", &city[i].x, &city[i].y, &city[i].w);

		for (int i = 0; i < N; i++)
		{
			g[i][i] = 0.0;                     // 这句其实用不到
			for (int j = i + 1; j < N; j++)    //
				g[i][j] = g[j][i] = getDistance(i, j);
		}
		prim();                                // 其重要任务是计算数组 max_edge[][]

		for (int i = 0; i < N; i++)
		{
			for (int j = i + 1; j < N; j++)    //
			{
				if (in_mst[i][j])
					ans = max(ans, ((double)(city[i].w + city[j].w)) / (mst - g[i][j]));
				else ans = max(ans, ((double)(city[i].w + city[j].w)) / (mst - max_edge[i][j]));
			}
		}
		printf("%.2f\n", ans);
	}

	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值