“在必须要一条非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中i
到j
路径上(有且只有一条路径)的最大边权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;
}