D141 次小生成树 Kruskal 算法+树上倍增 P4180 [BJWC2010] 严格次小生成树

D141 次小生成树 Kruskal 算法+树上倍增 P4180 [BJWC2010] 严格次小生成树_哔哩哔哩_bilibili

 

P4180 [BJWC2010] 严格次小生成树 - 洛谷          U592756 秘密的牛奶运输(次最小生成树弱化版) - 洛谷

求无向图的严格次小生成树的边权和。严格次小是比最小生成树的边权和要大。

思路

先求出最小生成树的边权和 $sum$

如果添加一条非树边 $(u,v,w)$,则形成一个环。设环上树边的最大边权为 $d1$,次大边权为 $d2$

如果 $w>d1$,则用非树边 $(u,v,w)$ 替换最大边 $d1$,得到候选答案 $sum-d1+w$

如果 $w=d1$,则用非树边 $(u,v,w)$ 替换次大边 $d2$,得到候选答案 $sum-d2+w$

枚举每条非树边,取候选答案的最小值

图中最小生成树为 10;非树边 6 可以替换树边 4,结果为 12;非树边 3 可以替换树边 2,结果为 11

如何快速找到一条路径上的最大边和次大边?

用树上倍增算法预处理:

$d1[u][i]$ 表示从 $u$ 点开始向上跳 $2^i$ 条边到达 $p$ 点,这条路径上的最大边权

$d2[u][i]$ 表示从 $u$ 点开始向上跳 $2^i$ 条边到达 $p$ 点,这条路径上的次大边权

$up$ 路径的中点 $o=fa[u][i-1]$,把路径拆成两段 $uo$ 和 $op$ 寻找

从 $\{d1[u][i-1],d1[o][i-1],d2[u][i-1],d2[o][i-1]\}$ 这 4 个数中排序求出 $d1[u][i],d2[u][i]$

相关板子:

D08【模板】最小生成树 Kruskal 算法 - 董晓 - 博客园

D09【模板】最近公共祖先 倍增算法 - 董晓 - 博客园

// 最小生成树 kruskal算法 O(M*logM)
#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int N=100005,M=300005,INF=0x3f3f3f3f;
int idx,h[N],to[M],ne[M],ww[M];
void add(int u,int v,int w){ //连边
  to[++idx]=v,ww[idx]=w,ne[idx]=h[u],h[u]=idx;
  to[++idx]=u,ww[idx]=w,ne[idx]=h[v],h[v]=idx;
}
int n,m; ll sum;
struct E{int u,v,w;}e[M]; //边集
bool used[M];
int fa[N]; //并查集的fa

int find(int u){ //并查集的找根
  return fa[u]==u?u:fa[u]=find(fa[u]);
}
void kruskal(){
  int tot=0;
  for(int i=1; i<=n; i++) fa[i]=i;
  sort(e+1,e+m+1,[&](E u,E v){return u.w<v.w;});
  for(int i=1; i<=m; i++){
    int u=find(e[i].u),v=find(e[i].v);
    if(u!=v){
      fa[u]=v;
      sum+=e[i].w;  //累加边权和
      used[i]=true; //记录树边
      add(e[i].u,e[i].v,e[i].w); //建最小生成树
      if(++tot==n-1) break;
    }
  }
}

struct Tree{
  int fa[N][18],dep[N];
  int d1[N][18]; //d1[u][i]表示从u点开始向上跳2^i条边,这条路径上的最大边权
  int d2[N][18]; //d2[u][i]表示从u点开始向上跳2^i条边,这条路径上的次大边权,不存在为-INF

  void dfs(int u,int f){ //预处理fa,d1,d2数组
    dep[u]=dep[f]+1; fa[u][0]=f; d2[u][0]=-INF;
    for(int i=1; i<=17; i++){
      fa[u][i]=fa[fa[u][i-1]][i-1];
      
      int d[4]={d1[u][i-1],d1[fa[u][i-1]][i-1],d2[u][i-1],d2[fa[u][i-1]][i-1]};
      sort(d,d+4);
      d1[u][i]=d[3]; //最大边权
      int p=2;
      while(p>=0 && d[p]==d[3]) p--;
      d2[u][i]=(p==-1?-INF:d[p]); //次大边权
    }
    
    for(int i=h[u]; i; i=ne[i]){
      int v=to[i],w=ww[i];
      if(v!=f){
        d1[v][0]=w;
        dfs(v,u);
      }
    }
  }
  int lca(int u,int v){ //倍增求lca
    if(dep[u]<dep[v]) swap(u,v);
    for(int i=17;i>=0;i--)if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
    if(u==v) return u;
    for(int i=17; i>=0; i--)if(fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i];
    return fa[u][0];
  }
  int query(int u,int v,int w){ //倍增求小于w的最大边权
    int res=-INF;
    for(int i=17; i>=0; i--){
      if(dep[fa[u][i]]>=dep[v]){
        if(w>d1[u][i]) res=max(res,d1[u][i]);
        else if(w==d1[u][i]) res=max(res,d2[u][i]);
        u=fa[u][i];
      }
    }
    return res;
  }
}T;

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};
  }
  kruskal();
  T.dfs(1,0);
  
  ll ans=1e18;
  for(int i=1; i<=m; i++)if(!used[i]){ //非树边
    auto [u,v,w]=e[i];
    int l=T.lca(u,v);
    ll w1=T.query(u,l,w),w2=T.query(v,l,w);
    ans=min(ans,sum-max(w1,w2)+w);
  }
  printf("%lld\n",ans);
}

 

posted @ 2026-04-19 08:41  董晓  阅读(152)  评论(0)    收藏  举报