次小生成树

最小生成树的求法无非就两种方法:一种是prim算法,一种是kruscal算法
prim算法类似于迪杰斯特拉算法,时间复杂度为O(n^2)
kruscal算法是每次枚举最小边,并查集检验是否成环即可,时间复杂度为O(elog(e))
两种方法都非常简单。。。。

现在扩展一种知识点,叫做次小生成树:
定义:给定一个带权图,把图中所有生成树按权值从小到大排序,第二小的成为次小生成树

计算次小生成树的两种方法:
方法1:先求最小生成树,再枚举删去最小生成树中的边求解,时间复杂度O(mlogm+n*m)
方法2:先求最小生成树,然后依次枚举非树边,然后将该边加入树中,同时从树中去掉一条边, 使得到的图仍是一棵树,则一定可以求出次小生成树

设T为图G的一颗生成树,对于非树边a和树边b,插入边a,并删除边b的操作即为(+a,-b)。
如果T+a-b之后,仍然是一颗生成树,称(+a,-b)是T的一个可行交换。
称由T进行一次可行变换所得到的新的生成树集合成为T的邻集。
定理:次小生成树一定在最小生成树的邻集中。

操作步骤:
1.求最小生成树,统计标记每条边是树边,还是非树边;同时把最小生成树建立出来
2.预处理任意两点间的边权最大值dis1[a][b]和次大边权dis2[a][b];(dfs遍历两点之间所有边权)
3.依次枚举所有非树边,看是否大于非树边所对应的起点a和终点b的边权最大值dis1[a][b],如果大于,那么就替换,否则再检查是否大于次大边权值dis2[a][b],操作与上亦同,由于非树边的权值可能与最大值相等,所以,要再考虑次大值。

例题:
acwing–1148. 秘密的牛奶运输
农夫约翰要把他的牛奶运输到各个销售点。
运输过程中,可以先把牛奶运输到一些销售点,再由这些销售点分别运输到其他销售点。
运输的总距离越小,运输的成本也就越低。
低成本的运输是农夫约翰所希望的。
不过,他并不想让他的竞争对手知道他具体的运输方案,所以他希望采用费用第二小的运输方案而不是最小的。
现在请你帮忙找到该运输方案。

注意:

如果两个方案至少有一条边不同,则我们认为是不同方案;

费用第二小的方案在数值上一定要严格小于费用最小的方案;

答案保证一定有解;

输入格式
第一行是两个整数 N,M,表示销售点数和交通线路数;
接下来 M 行每行 3 个整数x,y,z,表示销售点 x 和销售点 y 之间存在线路,长度为 z。输出格式
输出费用第二小的运输方案的运输总距离。数据范围
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OhCJsz5R-1628304561944)(/img/bVcSzEF)]

数据中可能包含重边。输入样例:

4 4
1 2 100
2 4 200
2 3 250
3 4 100
输出样例:
450

#include <algorithm>
#include <iostream>
#include <cstring>
#include <vector>

using namespace std;
struct Edge
{
    int x,y,w;
   bool op;
    bool operator<(const Edge &a)const{
        return w<a.w;
    }
}e[10001];
struct node
{
    int to,w;
};
vector<node>v[501];

int parent[501];
int Find(int x)
{
    if(x!=parent[x])
    {
        parent[x]=Find(parent[x]);
    }
    return parent[x];
}
int dist1[501][501],dist2[501][501];//记录两点之间边权最大值和次大值
void dfs(int u,int fa,int maxd1,int maxd2,int d1[],int d2[])//dfs找寻两节点之间最大边权值和次大边权值
{
    d1[u]=maxd1,d2[u]=maxd2;
    for(int i=0;i<v[u].size();i++)
    {
        int j=v[u][i].to;
        int w=v[u][i].w;
        if(j!=fa)//避免死循环
        {
            int t1=maxd1,t2=maxd2;
            //注意一定不可以相等,因为次小生成树要严格大于最小生成树
            if(w>maxd1)
            {
                t1=w;
                t2=maxd1;
            }
            else if(w<maxd1&&w>maxd2)
            {
                t2=w;
            }
            dfs(j,u,t1,t2,d1,d2);
        }
    }
}

int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        parent[i]=i;
    }
    for(int i=0;i<m;i++)
    {
        cin>>e[i].x>>e[i].y>>e[i].w;
    }
    sort(e,e+m);
    long long int sum=0;
    for(int i=0;i<m;i++)
    {
        int x=e[i].x,y=e[i].y;
        int tx=Find(x),ty=Find(y);
        if(tx!=ty)
        {
            parent[tx]=ty;
            e[i].op=1;
            sum+=e[i].w;
            v[e[i].x].push_back({e[i].y,e[i].w});
            v[e[i].y].push_back({e[i].x,e[i].w});
        }
    }
    for(int i=1;i<=n;i++)
    {
        dfs(i,-1,-0x3f3f3f3f,-0x3f3f3f3f,dist1[i],dist2[i]);
    }
    long long int ret=1e18;
    
    for(int i=0;i<m;i++)
    {
        if(!e[i].op)
        {
            if(e[i].w>dist1[e[i].x][e[i].y])
            {
                ret=min(ret,sum-dist1[e[i].x][e[i].y]+e[i].w);
            }
            else if(e[i].w>dist2[e[i].x][e[i].y])
            {
                ret=min(ret,sum-dist2[e[i].x][e[i].y]+e[i].w);
            }
        }
    }
    cout<<ret<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值