Kruskal(次小生成树) - 秘密的牛奶运输 - AcWing 1148

Kruskal(次小生成树) - 秘密的牛奶运输 - AcWing 1148

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

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

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

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

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

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

注意:

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

输入格式

第一行是两个整数 N,M,表示销售点数和交通线路数;

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

输出格式

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

数据范围

1 ≤ N ≤ 500 , 1 ≤ M ≤ 1 0 4 , 1 ≤ z ≤ 1 0 9 , 1≤N≤500, 1≤M≤10^4, 1≤z≤10^9, 1N500,1M104,1z109,
数据中可能包含重边。

输入样例:

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

输出样例:

450

分析:

分 析 题 意 知 , 本 题 最 终 是 要 求 次 小 生 成 树 。 分析题意知,本题最终是要求次小生成树。

求次小生成树步骤:

① 、 求 最 小 生 成 树 , 记 代 价 为 s u m 。 ①、求最小生成树,记代价为sum。 sum

② 、 从 非 树 边 ( 非 最 小 生 成 树 中 的 边 ) 中 选 择 一 条 边 , 假 设 这 条 边 的 两 个 端 点 分 别 是 i 和 j , 边 权 为 w i 。 ②、从非树边(非最小生成树中的边)中选择一条边,假设这条边的两个端点分别是i和j,边权为w_i。 ()ijwi

将 该 边 加 入 到 最 小 生 成 树 中 去 , 将 会 形 成 环 , 因 此 我 们 需 要 去 掉 一 条 环 上 的 树 边 。 \qquad将该边加入到最小生成树中去,将会形成环,因此我们需要去掉一条环上的树边。

为 了 生 成 次 小 生 成 树 , 我 们 在 环 上 找 到 最 大 的 树 边 删 去 , 记 边 权 为 d 1 [ i ] [ j ] 。 \qquad为了生成次小生成树,我们在环上找到最大的树边删去,记边权为d_1[i][j]。 d1[i][j]

若 环 上 最 大 的 树 边 与 添 加 的 新 边 权 值 相 等 , 我 们 就 删 除 环 上 的 次 小 树 边 , 记 边 权 为 d 2 [ i ] [ j ] 。 \qquad若环上最大的树边与添加的新边权值相等,我们就删除环上的次小树边,记边权为d_2[i][j]。 d2[i][j]

③ 、 得 到 次 小 生 成 树 代 价 为 m i n 1 ≤ i ≤ M ( s u m − d 1 [ i ] ( d 2 [ i ] ) + w i ) 。 ③、得到次小生成树代价为min_{1≤i≤M}(sum-d_1[i](d_2[i])+w_i)。 min1iM(sumd1[i](d2[i])+wi)

由 此 分 析 发 现 , 我 们 还 需 要 预 处 理 出 最 小 生 成 树 上 任 意 两 点 之 间 的 最 大 边 权 d 1 [ i ] [ j ] 和 次 大 边 权 d 2 [ i ] [ j ] 。 由此分析发现,我们还需要预处理出最小生成树上任意两点之间的最大边权d_1[i][j]和次大边权d_2[i][j]。 d1[i][j]d2[i][j]

我 们 可 以 在 求 最 小 生 成 树 的 过 程 中 , 把 树 边 用 邻 接 表 存 储 , 再 做 树 上 d f s 预 处 理 数 组 d 1 和 d 2 。 我们可以在求最小生成树的过程中,把树边用邻接表存储,再做树上dfs预处理数组d_1和d_2。 dfsd1d2

也 就 是 对 于 树 上 的 任 意 两 点 i 和 j , 用 i 和 j 之 间 的 最 大 边 权 更 新 d 1 [ i ] [ j ] , 次 大 边 权 更 新 d 2 [ i ] [ j ] 。 也就是对于树上的任意两点i和j,用i和j之间的最大边权更新d_1[i][j],次大边权更新d_2[i][j]。 ijijd1[i][j]d2[i][j]

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

#define ll long long

using namespace std;

const int N=510, M=10010*2;

int n,m;
int e[M],ne[M],w[M],h[N],idx;   //存树边
int p[N],dis1[N][N],dis2[N][N];

struct edge
{
    int u,v,w;
    bool f;     //标记树边
    bool operator < (const edge &t) const
    {
        return w<t.w;
    }
}E[M];

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

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

ll kruskal()
{
    sort(E,E+m);
    
    ll ans=0;
    for(int i=0;i<m;i++)
    {
        int u=E[i].u,v=E[i].v,w=E[i].w;
        int pu=Find(u),pv=Find(v);
        if(pu!=pv)
        {
            ans+=w;
            p[pu]=pv;
            add(u,v,w),add(v,u,w);
            E[i].f=true;
        }
    }
    
    return ans;
}

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];  //严格次大,必须有w[i]<t1
            dfs(j,u,t1,t2,d1,d2);
        }
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    memset(h,-1,sizeof h);  //邻接表表头初始化!
    for(int i=1;i<=n;i++) p[i]=i;
    for(int i=0;i<m;i++) scanf("%d%d%d",&E[i].u,&E[i].v,&E[i].w);
    
    ll sum=kruskal();
    
    for(int i=1;i<=n;i++) dfs(i,-1,0,-1e9,dis1[i],dis2[i]);   //初始化次小边不存在,初始化为-1e9
    
    ll res=1e18;
    for(int i=0;i<m;i++)
        if(!E[i].f)
        {
            int u=E[i].u,v=E[i].v,w=E[i].w;
            ll t;
            if(w-dis1[u][v]>0) t=sum+w-dis1[u][v];
            else if(w-dis2[u][v]>0) t=sum+w-dis2[u][v];
            res=min(res,t);
        }
    
    cout<<res<<endl;
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值