农夫约翰要把他的牛奶运输到各个销售点。
运输过程中,可以先把牛奶运输到一些销售点,再由这些销售点分别运输到其他销售点。
运输的总距离越小,运输的成本也就越低。
低成本的运输是农夫约翰所希望的。
不过,他并不想让他的竞争对手知道他具体的运输方案,所以他希望采用费用第二小的运输方案而不是最小的。
现在请你帮忙找到该运输方案。
注意::
- 如果两个方案至少有一条边不同,则我们认为是不同方案;
- 费用第二小的方案在数值上一定要严格大于费用最小的方案;
- 答案保证一定有解;
输入格式
第一行是两个整数 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
思路:
/*
次小生成树
定义:给定一个带权的图 把图的所有生成树按权值从小到大排序,第二小的称为次小生成树
方法1:
先求最小生成树,再枚举删去最小生成树中的边求解
方法2:
先求最小生成树,然后依次枚举非树边,然后将该边加入树中,同时从树中去掉一条边,
使得最终的图仍是一棵树,则一定可以求出次小生成树
设T为图G的一棵生成树,对于非树边a和树边b,插入边a,并删除边b的操作记为(+a,-b)
如果T+a-b之后,仍然是一棵生成树,称(+a,-b)是T的一个可行交换
称由T进行一次可行变换所得到的新的生成树集合为T的邻集
定理:次小生成树一定在最小生成树的邻集中
1 求最小生成树,统计标记每条边是树边,还是非树边,同时把最小生成树建立出来
2 预处理任意两点间的边权最大值dist[a][b]
3 依次枚举所有非树边w,求min(sum+w(新边)-dist[a][b](老边))
严格最小:满足w>dist[a][b]
在求严格次小生成树时,不能只预处理两点之间最大的树边,
因为当最大树边和当前枚举的非树边长度相同时,就不能替换了,
但此时却可以替换长度次大的树边。因此还需同时预处理出长度次大的树边。
*/
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 510, M = 10010, INF = 0x3f3f3f3f;
int f[N];
int dist1[N][N], dist2[N][N];
int st[N];
int h[N], e[N * 2], ne[N * 2], w[N * 2], idx;
struct Edge
{
int a, b, w;
bool f;
bool operator<(const Edge &W) const
{
return w < W.w;
}
} edge[M];
int n, m;
LL sum;
int find(int x)
{
if (f[x] != x)
f[x] = find(f[x]);
return f[x];
}
void add(int a, int b, int c)
{
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx++;
}
void merge(int s)
{
int a = edge[s].a, b = edge[s].b, c = edge[s].w;
int t1 = find(a);
int t2 = find(b);
if (t1 != t2)
{
f[t1] = t2;
edge[s].f = 1;
sum += c;
add(a, b, c), add(b, a, c);
}
}
void dfs(int u, int max1, int max2, int d1[], int d2[])
{
d1[u] = max1, d2[u] = max2;
st[u] = 1;
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j])
{
int td1 = max1, td2 = max2;
if (w[i] > td1) // 严格最长边
td2 = td1, td1 = w[i];
else if (w[i] < td1 && w[i] > td2) // 严格第二长边
td2 = w[i];
dfs(j, td1, td2, d1, d2);
}
}
}
int main()
{
cin >> n >> m;
memset(h, -1, sizeof(h));
for (int i = 0; i < m; i++)
scanf("%d %d %d", &edge[i].a, &edge[i].b, &edge[i].w);
sort(edge, edge + m);
for (int i = 1; i <= n; i++)
f[i] = i;
for (int i = 0; i < m; i++)
merge(i);
for (int i = 1; i <= n; i++)
{
memset(st, 0, sizeof(st));
dfs(i, -INF, -INF, dist1[i], dist2[i]);
}
LL res = 1e18;
for (int i = 0; i < m; i++)
{
if (!edge[i].f) // 非树边加入之后必定大于等于环上的其他边 否则替换则会更小
{
int a = edge[i].a, b = edge[i].b, w = edge[i].w;
LL t;
if (w > dist1[a][b])
t = sum + w - dist1[a][b];
else if (w > dist2[a][b]) // 或者 w==dist1[a][b] 或者直接 else
t = sum + w - dist2[a][b];
res = min(res, t);
}
}
cout << res << endl;
return 0;
}