次小生成树问题——秘密的牛奶运输

题目描述

image-20220219220155610

算法

本题是一个裸的严格次小生成树问题。所谓严格就是该次小生成树的总边权严格大于最小生成树(以下用MGT称呼)。非严格的就是边权和MGT相同

算法1:

自然的想先求MGT,再每次删去MGT中的一条边,在该图上做MGT求解(新图的MGT就是原图的次小生成树)。做一遍kruskal后面就不用再排序了,删边次数n - 1,每次求MGT是m,故 O ( m l o g m + n m ) O(mlogm + nm) O(mlogm+nm)

  • 这种方法求不出 严格次小生成树,因为如果有非严格次小生成树,删边之后再找肯定会找到非严。到底小不小是按边权和排序,非严是小于严的

虽然不能求严格,但也还是有用。做法的正确性证明如下:

  • 将所有权值大于MGT的生成树集合S,按照不包含最小生成树中的第1条边,第2条边…第n - 1条边分类,能把S不漏的分完。故每类取min就能得到次小生成树。我们删边之后做MGT就是在取每一个类的最小值。

算法2:

.先求最小生成树,然后依次枚举非树边,然后将该边加入树中,同时从树中去掉一条边,使得最终图仍然是一棵树,统计最小值。

这个方法一定可以找到非严。证明如下

  • 定义1:设T为图G的一棵生成树,对于非树边a和树边b,插入边a,并删除边b的操作记为(+a, -b)。如果T+a-b之后,仍然是一棵生成树,称(+a,-b)是T的一个可行交换。
  • 定义2:称由T进行一次可行变换所得到的新的生成树集合称为T的邻集。
  • 做法中的枚举外部边,删除树中边就是枚举MGT的邻集。
  • 定理:次小生成树一定在最小生成树的邻集中
  • 定理证明:
    • 反证,如果有次小生成树与最小生成树不相同的有k条边,排序做kruskal,找到第一条和次小不同的边,在树中的边t连接ab。
    • 我们将次小中连接ab中的一条边p去掉换成t,那么次小就会变得更小(因为排序kruskal)这样一个可行交换我们让次小生成树和最小生成树的不相同的边数k-1。这种操作可以持续做下去直到只差1条边。所以次小生成树一定在最小生成树的邻集中。

image-20220219222009913

  • 故该算法能找到非严格次小生成树。

该算法的详细步骤如下

  • 求最小生成树,统计标记每条边是否是树边;同时把最小生成树建立,权值之和为 s u m sum sum

  • 预处理生成树中任意两点间的边权最大值 d i s t 1 [ a ] [ b ] dist1[a][b] dist1[a][b]和长度次大 d i s t 2 [ a ] [ b ] dist2[a][b] dist2[a][b] (树中两点路径唯一,dfs)

  • 依次枚举所有非MGT边t,边t连接a,b,权为w。显然a,b在MGT中。

  • 尝试用t替换a-b的路径中最大的一条边A,A权为a。w > a。(若w < a,t边是外部边,直接换边就能得到更小的生成树,矛盾了)

  • 如果w > a,替换后总权值是 s u m + w − d i s t 1 [ a ] [ b ] sum + w - dist1[a][b] sum+wdist1[a][b]

  • 否则 w = a ,不能替换A边,会得到非严格次小生成树(权值和MGT相等) 所以该做法也能得到非严

  • w = a,w > 次大值b 则可以替换b的边,替换后总权值是$sum + w - dist2[a][b] $

终于分析完了,代码+注释如下

/*
 * @Author: 爱学习的图灵机
 * @Date: 2022-02-19 19:56:58
 * @LastEditTime: 2022-02-19 21:35:34
 * Bilibili:https://space.bilibili.com/7469540
 * 题目地址:https://www.acwing.com/activity/content/problem/content/1529/
 * @keywords: 最小生成树次小生成树
将最小生成树称为为MGT
次小生成树在最小生成树的邻集中。(最小生成树变一个边)

求最小生成树,统计标记每条边是否是树边;同时把最小生成树建立,权值之和为sum

预处理生成树中任意两点间的边权最大值dist1[a][b]和长度次大dist2[a][b] (树中两点路径唯一,dfs)

依次枚举所有非MGT边t,边t连接a,b,权为w。显然a,b在MGT中。
    尝试用t替换a-b的路径中最大的一条边A。t的权w >= A。(如果w < A,直接换边就能得到更小的生成树,矛盾了)
    如果w > A,替换后总权值是sum + w - dist1[a][b] 
    否则 w = A ,不能替换,会得到非严格次小生成树(权值和MGT相等)
    w = A,w > 次大值B 替换后总权值是sum + w - dist2[a][b] 

 */
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 510, M = 10010;
typedef long long LL;

struct Edge{
    int a, b, w;
    bool f = false;
    bool operator < (const Edge & A) const{
        return w < A.w;
    }
}edge[M];

int h[N], e[N * 2], ne[N * 2], w[N * 2], idx; // 无向树 2 * N
int cnt;
int n, m;
int dist1[N][N], dist2[N][N];// 最小和次小
int p[N];
void add(int a,int b,int c){
    e[idx] = b,w[idx] = c,ne[idx] = h[a],h[a] = idx ++;
}
int find(int x){
    return x == p[x] ? x : p[x] = find(p[x]);
}

//dfs无向图技巧:记录父节点防止回走
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 td1 = maxd1,  td2 = maxd2;
            if (w[i] > td1) td2 = td1, td1 = w[i];
            else if (w[i] < td1 && w[i] > td2) td2 = w[i];
            dfs(j, u, td1, td2, d1, d2);
        }
    }
}


int main()
{
    memset(h, -1, sizeof h);
    cin >> n >> m;
    ;
    for(int i = 0; i < m; ++ i){
        int a, b, w;
        cin >> a >> b >> w;
        edge[i] = {a, b, w}; 
    }
    
    sort(edge, edge + m);
    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].f = true;
        }
    }
    
    for(int i = 1; i <= n; ++ i)dfs(i, -1, -1e9,-1e9, dist1[i],dist2[i]);// 生成树内搜索最长路
    
    LL res = 1e18;

    for(int i = 0; i < m; ++ i){ 
        int a = edge[i].a, b = edge[i].b, w = edge[i].w;
 
        if(!edge[i].f){// 遍历每条外部边尝试替换
            LL t = 1e18;
            if(w > dist1[a][b]){
                t = sum + w - dist1[a][b];
            }else if( w > dist2[a][b]){ // w不是大于就是等于
                t = sum + w - dist2[a][b];
            }
            res = min(res, t);
        }
    }
    cout << res << endl;
    
    
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值