USACO 秘密的牛奶运输 次小生成树

一、内容

秘密的牛奶运输

农夫约翰要把他的牛奶运输到各个销售点。

运输过程中,可以先把牛奶运输到一些销售点,再由这些销售点分别运输到其他销售点。

运输的总距离越小,运输的成本也就越低。

低成本的运输是农夫约翰所希望的。

不过,他并不想让他的竞争对手知道他具体的运输方案,所以他希望采用费用第二小的运输方案而不是最小的。

现在请你帮忙找到该运输方案。

注意::

如果两个方案至少有一条边不同,则我们认为是不同方案;
费用第二小的方案在数值上一定要严格小于费用最小的方案;
答案保证一定有解;
输入格式
第一行是两个整数 N,M,表示销售点数和交通线路数;

接下来 M 行每行 3 个整数 x,y,z,表示销售点 x 和销售点 y 之间存在线路,长度为 z。

输出格式

输出费用第二小的运输方案的运输总距离。

数据范围

1≤N≤500,
1≤M≤104,
1≤z≤109,
数据中可能包含重边。

输入样例:

4 4
1 2 100
2 4 200
2 3 250
3 4 100

输出样例:

450

二、思路

  • 根据存在次小生成树与最小生成树只差一条边
  • 我们可以先通过kruskal求出最小生成树 O(mlogm)
  • 通过这个树初始化某点到其他点的路径中最大边、次大边。 O(n2)
  • 遍历所有未在树中的边(u–>v), 看是否大于u到v的边的最大值, 若大于那么可以替换这条边,若不大于那就再判断是否大于次值,若大于同理进行替换。
    在这里插入图片描述

三、代码

#include <cstdio>
#include <algorithm> 
typedef long long ll; 
using namespace std;
const int N = 505, M = 1e4 + 5;
struct Edge {
	int u, v, w;
	bool in; //in代表这条边是否在树中 
	bool operator < (const Edge&o) const {
		return w < o.w;
	} 
} edge[M]; 
struct E {
	int v, w, next;
} e[N * 2]; //树的边有N-1条*双向
int n, m, len, h[N], md1[N][N], md2[N][N], p[N]; //md[i][j] 代表i到j点的路径上最大的一条边 
int find(int x) { return x == p[x] ? x : (p[x] = find(p[x]));}
void add(int u, int v, int w) {
	e[++len].v = v; e[len].w = w; e[len].next = h[u]; h[u] = len;
}
void dfs(int s, int u, int fa, int mw1, int mw2) {
	//从s起点出发到其他点 
	md1[s][u] = mw1; md2[s][u] = mw2;
	for (int j = h[u]; j; j = e[j].next) {
		int v = e[j].v; 
		int w = e[j].w;
		if (v != fa) {
			int t1, t2; 
			if (w > mw1) t1 = w, t2 = mw1;
			//这里不能相等 
			else if (w < mw1 && w > mw2) t1 = mw1, t2 = w; 
			dfs(s, v, u, t1, t2);
		}
	}
}
int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) p[i] = i; 
	for (int i = 0; i < m; i++) scanf("%d%d%d", &edge[i].u, &edge[i].v, &edge[i].w);
	sort(edge, edge + m);
	//求出最小生成树 
	ll sum = 0; //求出最小生成树的边权和 
	for (int i = 0; i < m; i++) {
		int u = edge[i].u, v = edge[i].v , w = edge[i].w;
		int fu = find(u), fv = find(v);
		if (fu != fv) {
			sum += w;
			add(u, v, w); add(v, u, w); //构成树
			edge[i].in = true; //代表在树中 
			p[fu] = fv;
		}
	}  
	//通过树 求出i到其他点的路径中的最大边
	for (int i = 1; i <= n; i++) dfs(i, i, -1, 0, 0); 
	ll ans = 1e18;
	for (int i = 0; i < m; i++) {
		if (!edge[i].in) {
			//如果这条边不在最小生成树中 考虑替换
			int w = edge[i].w, u = edge[i].u, v = edge[i].v;
			//如果这条边比u到v的路径中的最大边还大 那么可以替换 不然替换了反而变小 
			if (w > md1[u][v]) ans = min(ans, sum + w - md1[u][v]);
			//如果和最大的边相等 那么判断是否大于次大边 
			else if (w > md2[u][v]) ans = min(ans, sum + w - md2[u][v]);
		} 
	}
	printf("%lld", ans);
	return 0;
} 
发布了414 篇原创文章 · 获赞 380 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_41280600/article/details/104227780