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

题干:

农夫约翰要把他的牛奶运输到各个销售点。
运输过程中,可以先把牛奶运输到一些销售点,再由这些销售点分别运输到其他销售点。
运输的总距离越小,运输的成本也就越低。
低成本的运输是农夫约翰所希望的。
不过,他并不想让他的竞争对手知道他具体的运输方案,所以他希望采用费用第二小的运输方案而不是最小的。
现在请你帮忙找到该运输方案。
注意::
如果两个方案至少有一条边不同,则我们认为是不同方案;
费用第二小的方案在数值上一定要严格小于费用最小的方案;
答案保证一定有解;
输入格式
第一行是两个整数 N,M,表示销售点数和交通线路数;
接下来 MM 行每行 3 个整数 x,y,z,表示销售点 x 和销售点 y 之间存在线路,长度为 z。
输出格式
输出费用第二小的运输方案的运输总距离。

数据范围
1 ≤ N ≤ 500 , 1≤N≤500, 1N500,
1 ≤ M ≤ 1 0 4 , 1≤M≤10^4, 1M104,
1 ≤ z ≤ 1 0 9 , 1≤z≤10^9, 1z109,
数据中可能包含重边。
输入样例:
4 4
1 2 100
2 4 200
2 3 250
3 4 100
输出样例:
450

思路:

题干很直白的告诉了我们要求一个次小生成树,也就是说和最小生成树只有一条边不同。
将加入最小生成树的边称为树边,其余边称为非树边,则就是枚举选择一条树边去除,选择一条非树边加入。
当我们选择一条非树边(x,y,z)加入最小生成树后,肯定形成环(因为最小生成树已经是极小连通子图),则在形成的环中找最大边权mx1,严格次大边权mx2(mx1>mx2)
①z>mx1:换掉最大边权(因为是次小,所以只能换最大的)
②z=mx1:换掉严格次大边权(因为是次小,最大的边权又相等,所以换严格次大)
枚举所有非树边,这样最后得到的就是次小生成树。
现在问题转换成了求一条路径上的最大边权和严格次大边权
(1)树上倍增
(2)我们将树边新建一个图(注意是按原点建图,不是按并查集之后的父点),然后划分连通块,这样一个连通块就代表一个非树边加入后的环,然后在当前连通块找最大边权和严格次大边权。
dfs划分过程中记录最大值和次大值就行,每个点出发对应的值用数组存储

//2、dfs划分连通块
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
typedef long long ll;
struct stu{
    int x,y,w;
    bool f;
    bool operator< (const stu &t) const
    {
        return w < t.w;
    }
}edge[10100];
struct E{
    int next,w;
};
int F[10100],dis1[510][510],dis2[510][510];
vector<E> tu[510];
int find(int i){
    if(F[i]==i)
        return i;
    return F[i]=find(F[i]);
}
void add(int a,int b,int c){  //建立新图
    E t;
    t.next=b;
    t.w=c;
    tu[a].push_back(t);
}
void dfs(int u,int fa,int mx1,int mx2,int d1[],int d2[]){//划分连通块并找两个值
    d1[u]=mx1;
    d2[u]=mx2;
    for(int i=0;i<tu[u].size();i++){
        int  v=tu[u][i].next,w=tu[u][i].w;
        int td1=mx1,td2=mx2;
        if(v!=fa){
            if(w>td1){
                td2=td1;
                td1=w;
            }
            else if(w>td2){
                td2=w;
            }
            dfs(v,u,td1,td2,d1,d2);
        }
    }
}

int main(){
    int n,m,a,b,c;
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++){
        scanf("%d%d%d",&a,&b,&c);
        edge[i]={a,b,c};
    }
    sort(edge,edge+m);
    for(int i=0;i<=n;i++)
        F[i]=i;
    ll ans=0;
    
    for(int i=0;i<m;i++){
        a=find(edge[i].x);b=find(edge[i].y);
        c=edge[i].w;
        //printf("%d %d %d\n",a,b,c);
        if(a!=b){
            ans+=c;
            F[a]=b;
            edge[i].f=true; //记录树边
            add(edge[i].x,edge[i].y,c);
            add(edge[i].y,edge[i].x,c);
        }
    }
    //printf("%d\n",ans);
    for(int i=1;i<=n;i++){ //从点i出发
        dfs(i,-1,0,0,dis1[i],dis2[i]);
    }
    ll res=1e18;
    for(int i=0;i<m;i++){ //遍历非树边
        if(!edge[i].f){
            a=edge[i].x,b=edge[i].y,c=edge[i].w;
            ll t=1e18;
            if(c>dis1[a][b]){  //判断是否可以加入
               t=ans+c-dis1[a][b]; 
            }
            else if(c>dis2[a][b]){
                t=ans+c-dis2[a][b]; 
            }
            res=min(res,t);
            
        } 
    }
    printf("%lld\n",res);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值