kruskal求严格次小生成树--ACwing1148秘密的牛奶运输

本文探讨了农夫约翰的牛奶运输问题,旨在找到费用第二小的运输方案。通过使用Kruskal算法求解最小生成树,并结合边的次大权重,确定次小生成树。算法思路包括先构建最小生成树,然后遍历所有边,替换能减少总费用的边,最终输出次小的运输总距离。
题目描述(抽象模型)

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

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

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

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

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

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

注意:

  • 如果两个方案至少有一条边不同,则我们认为是不同方案;
  • 费用第二小的方案在数值上一定要严格小于费用最小的方案;
  • 答案保证一定有解;
输入格式

第一行是两个整数 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. 先用kruskal求出最小生成树
  3. 求出每个两个点之间距离的最大值和次大值
  4. 遍历所有不在树中的边,若边权大于该边两点路径上边的最大值,则用该边取代最大的边,并更新答案,若边权等于该边两点路径上的最大值,则用该边取代次大边,并更新答案,由于是最小生成树,所以不存在该边小于最大边的情况
  5. 输出答案
AC代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 510, M = 10010;
typedef long long LL;
int n, m, p[N], dist1[N][N], dist2[N][N];
struct node {
    int a, b, w;
    bool flag;
}edge[M];

int h[N], e[N * 2], ne[N * 2], w[N * 2], idx;

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx++;
}

bool cmp(node a, node b)
{
    return a.w < b.w;
}

int find(int x)
{
    if(x != p[x]) p[x] = find(p[x]);
    return p[x];
}

void dfs(int u, int fa, int maxd1, int maxd2, int d1[], int d2[])
{
    d1[u] = maxd1, d2[u] = maxd2;
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if(j != fa)
        {
            int t1= maxd1, t2 = maxd2;
            if(w[i] > t1) t2 = t1, t1 = w[i];
            else if(w[i] < t1 && w[i] > t2) t2 = w[i];
            dfs(j, u, t1, t2, d1, d2);
        }
    }
}

int main(void)
{
    cin >> n >> m;
    memset(h, -1, sizeof(h));
    for(int i = 0; i < m; i++)
    {
        int a, b, w;
        cin >> a >> b >> w;
        edge[i] = {a, b, w};
    }
    
    sort(edge, edge + m, cmp);
    
    for(int i = 1; i <= n; i++) p[i] = i;
    
    LL sum = 0;
    for(int i = 0; i < m; i++)
    {
        int a = edge[i].a, b = edge[i].b, w = edge[i].w;
        int pa = find(a), pb = find(b);
        
        if(pa != pb)
        {
            p[pa] = pb;
            sum += w;
            add(a, b, w), add(b, a, w);
            edge[i].flag = true;
        }
    }
    
    for(int i = 1; i <= n; i++) dfs(i, -1, 0, 0, dist1[i], dist2[i]);
    
    LL res = 1e18;
    for(int i = 0; i < m; i++)
    {
        if(!edge[i].flag)
        {
            int a = edge[i].a, b = edge[i].b, w = edge[i].w;
            if(w > dist1[a][b])
            {
                res = min(res, sum + w - dist1[a][b]);
            }
            else res = min(res, sum + w - dist2[a][b]);
        }
    }
    cout << res << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值