次小生成树

定义

在这里插入图片描述
有严格次小以及非严格次小;

严格次小就是权值和必须比最小的小;

非严格次小,也就是说最小生成树不唯一;

方法

在这里插入图片描述

方法一解释

这个比较容易想到,每条边都去掉试试看,一定可以枚举完所有的解;

方法二解释

在这里插入图片描述

简单来说就是下面这张图;
在这里插入图片描述

例题

次小生成树

题面

在这里插入图片描述
在这里插入图片描述

思路

我们可以先构建出最小生成树;

然后枚举边,如果这条边加进去会形成环;

那么我们就用这条边替换最小生成树上最大的边;

如果这条边与最大的边相等,那么就替换次大的边;

试完所有连接会形成环的边以后,我们就得到了次小生成树;


d i s t ( i , j ) 表 示 点 i 往 上 2 j dist(i,j)表示点i往上2^j dist(i,j)i2j所得到的最大边权

d i s t 2 ( i , j ) 表 示 点 i 往 上 2 j dist2(i,j)表示点i往上2^j dist2(i,j)i2j所得到的次大边权

使用LCA来询问这个环上除了当前边的所有边权;

Code

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

using namespace std;

const int N = 1e5+10,M = 3e5+10;

typedef long long ll;

const int INF = 0x3f3f3f3f;

int n,m,cnt,head[N],p[N];
                       //最大值    次大值
int depth[N],fa[N][20],dist[N][20],dist2[N][20];

struct Edge{
    int u,v,val;
    bool used;
}e[M];

struct Node{
    int to,next,val;
}edge[M];

void add(int u,int v,int w){
    ++cnt;
    edge[cnt].val = w;
    edge[cnt].to = v;
    edge[cnt].next = head[u];
    head[u] = cnt;
}

int find(int x){
    if(x == p[x]) return x;
    return p[x] = find(p[x]);
}
ll kruskal(){
   for(int i=1;i<=n;++i) p[i] = i;
   sort(e+1,e+1+m,[](Edge p,Edge q)->bool
        {
           return p.val < q.val;
        });
   ll ret = 0;
   for(int i=1,u,v,fu,fv;i<=m;++i){
        u = e[i].u;
        v = e[i].v;
        fu = find(u);
        fv = find(v);
        if(fu != fv){
            p[fu] = fv;
            ret += e[i].val;
            e[i].used = 1;
        }
   }
   return ret;
}
void build(){
    for(int i=1,u,v,w;i<=m;++i){
        if(e[i].used){
            u = e[i].u;
            v = e[i].v;
            w = e[i].val;
            add(u,v,w);
            add(v,u,w);
        }
    }
}
//初始化LCA
void bfs(int root){
    memset(depth,0x3f,sizeof depth);
    depth[0] = 0;
    depth[root] = 1;
    queue<int> q;
    q.push(root);
    while(!q.empty()){
        int u = q.front();
        q.pop();
        for(int i=head[u];i;i=edge[i].next){
            int to = edge[i].to;
            if(depth[to] > depth[u] + 1){
                depth[to] = depth[u] + 1;
                q.push(to);
                fa[to][0] = u;
                dist[to][0] = edge[i].val;
                dist2[to][0] = -INF;
                for(int k=1;k<=19;++k){
                    int anc = fa[to][k-1];
                    fa[to][k] = fa[anc][k-1];
                    dist[to][k] = dist2[to][k] = -INF;
                    //从两个最大值、两个次大值转移
                    int distant[4] = {
                        dist[to][k-1],dist[anc][k-1],
                        dist2[to][k-1],dist2[anc][k-1]
                    };
                    for(int d=0;d<4;++d){
                        int dis = distant[d];
                        if(dis > dist[to][k]){
                            //最大值降为次大值
                            dist2[to][k] = dist[to][k];
                            //新的最大值
                            dist[to][k] = dis;
                        }//dis严格比最大值小
                        else if(dis!=dist[to][k]&&dis>dist2[to][k]){
                            dist2[to][k] = dis;
                        }
                    }
                }
            }
        }
    }
}
//这里查询的不是祖先
//而是替换掉能替换的最大的一条边
vector<int> disv;//存储每条链的距离
int LCA(int u,int v,int w){
    disv.clear();
    if(depth[u] < depth[v]){
        swap(u,v);
    }
    //跳到同一层
    for(int k=19;k>=0;--k){
        if(depth[fa[u][k]] >= depth[v]){
            disv.push_back(dist[u][k]);
            disv.push_back(dist2[u][k]);
            u = fa[u][k];
        }
    }
    //一起往上跳
    if(u!=v){
        for(int k=19;k>=0;--k){
            if(fa[u][k]!=fa[v][k]){
                disv.push_back(dist[u][k]);
                disv.push_back(dist2[u][k]);
                disv.push_back(dist[v][k]);
                disv.push_back(dist2[v][k]);
                u = fa[u][k];
                v = fa[v][k];
            }
        }
        disv.push_back(dist[u][0]);
        disv.push_back(dist2[u][0]);
        disv.push_back(dist[v][0]);
        disv.push_back(dist2[v][0]);
    }
    //环上的最大边和次大边
    int dis1 = -INF,dis2 = -INF;
    //处理除了u,v这条边以外,环上的所有边
    for(auto d : disv){
        if(d > dis1){
            dis2 = dis1;
            dis1 = d;
        }//如果d和dis1相等了,那dis1和dis2就相等了
        //那么我们构造次小生成树就失败了
        else if(dis1 != d && d > dis2){
            dis2 = d;
        }
    }
    //接上u,v这条边,替换掉最大边
    if(w > dis1){
        return w - dis1;
    }
    //替换掉次大边
    return w - dis2;
    //如果存在dis1 == dis2 那么返回-INF即可
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1,u,v,w;i<=m;++i){
        scanf("%d%d%d",&u,&v,&w);
        e[i] = {u,v,w,false};
    }
    //最小生成树
    ll mst = kruskal();
    build();
    bfs(1);//随便取个点当root
    ll sec_mst = 1e18;
    for(int i=1,u,v,w;i<=m;++i){
        //看看连各个边的情况
        if(e[i].used == false){
            u = e[i].u,v = e[i].v,w = e[i].val;
            sec_mst = min(sec_mst,mst + LCA(u,v,w));
        }
    }
    printf("%lld\n",sec_mst);
    return 0;
}


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值