【洛谷P4180】严格次小生成树

题目大意:给定一个 N 个顶点,M 条边的带权无向图,求该无向图的一个严格次小生成树。

引理:有至少一个严格次小生成树,和最小生成树之间只有一条边的差异。

题解:
通过引理可以想到一个暴力,即:先求出最小生成树,并记录树边,再枚举删除 MST 中的每一条边,每次重新做一次最小生成树算法,并将计算出来的所有结果取最小值即为答案。以 Kruskal 算法为例,暴力的时间复杂度为 \(O(n^2logn)\)
现在可以考虑在已知最小生成树的基础上,枚举每条非树边,将该边加入最小生成树中,并删去加入边的两个端点之间的任意一条边即可。由于要求次小生成树,显然删去的边权要尽可能大,又由于要求严格次小,为了避免非树边权值和端点路径上最大边权相同,需要记录一下端点路径上的严格次大边权。由此,问题转化成了如何求解一棵树上任意两点之间的最大边权和严格次大边权。直接考虑倍增即可。时间复杂度为 \(O(nlogn)\)

代码如下

#include <bits/stdc++.h>
#define mp make_pair
using namespace std;
const int maxn=1e5+10;
const int maxe=3e5+10;
typedef pair<long long,long long> P;

inline int read(){
    int x=0,f=1;char ch;
    do{ch=getchar();if(ch=='-')f=-1;}while(!isdigit(ch));
    do{x=x*10+ch-'0';ch=getchar();}while(isdigit(ch));
    return f*x;
}

struct rec{
    int from,to;
    long long w;
}edge[maxe];
bool cmp(const rec &a,const rec &b){return a.w<b.w;}
struct node{
    int nxt,to;
    long long w;
}e[maxn<<1];
int tot=1,head[maxn];
inline void add_edge(int from,int to,long long w){
    e[++tot]=node{head[from],to,w},head[from]=tot;
}

int n,m,fa[maxn],f[maxn][21],dep[maxn];
long long mx1[maxn][21],mx2[maxn][21],ans,mst;
bool used[maxe];

int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}

void kruskal(){
    sort(edge+1,edge+m+1,cmp);
    for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=1,sum=n;i<=m;i++){
        if(sum==1)break;
        int x=find(edge[i].from),y=find(edge[i].to);
        if(x!=y){
            used[i]=1,mst+=edge[i].w,fa[x]=y,--sum;
            add_edge(edge[i].from,edge[i].to,edge[i].w);
            add_edge(edge[i].to,edge[i].from,edge[i].w);
        }
    }
}

inline void upd(long long &max1,long long &max2,long long x,long long y){
    if(max1==x)max2=max(max2,y);
    else if(max1<x)max2=max1,max1=x,max2=max(max2,y);
    else max2=max(max2,x);
}

void dfs(int u,int fa){
    for(int i=1;i<=20;i++){
        f[u][i]=f[f[u][i-1]][i-1];
        mx1[u][i]=mx1[u][i-1],mx2[u][i]=mx2[u][i-1];
        upd(mx1[u][i],mx2[u][i],mx1[f[u][i-1]][i-1],mx2[f[u][i-1]][i-1]);
    }
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;if(v==fa)continue;
        dep[v]=dep[u]+1,f[v][0]=u,mx1[v][0]=e[i].w;
        dfs(v,u);
    }
}

P lca(int x,int y){
    long long max1=0,max2=0;
    if(dep[x]<dep[y])swap(x,y);
    for(int i=20;i>=0;i--)
        if(dep[f[x][i]]>=dep[y]){
            upd(max1,max2,mx1[x][i],mx2[x][i]);
            x=f[x][i];
        }
    if(x==y)return mp(max1,max2);
    for(int i=20;i>=0;i--)
        if(f[x][i]!=f[y][i]){
            upd(max1,max2,mx1[x][i],mx2[x][i]);
            upd(max1,max2,mx1[y][i],mx2[y][i]);
            x=f[x][i],y=f[y][i];
        }
    upd(max1,max2,mx1[x][0],mx2[x][0]);
    upd(max1,max2,mx1[y][0],mx2[y][0]);
    return mp(max1,max2);
}

void read_and_parse(){
    n=read(),m=read();
    for(int i=1;i<=m;i++)edge[i].from=read(),edge[i].to=read(),edge[i].w=read();
    kruskal();
    dfs(1,0);
}

void solve(){
    ans=0x3f3f3f3f3f3f3f3f;
    for(int i=1;i<=m;i++)if(!used[i]){
        P tmp=lca(edge[i].from,edge[i].to);
        if(edge[i].w==tmp.first&&tmp.second)ans=min(ans,edge[i].w-tmp.second);
        else if(edge[i].w>tmp.first)ans=min(ans,edge[i].w-tmp.first);
    }
    printf("%lld\n",mst+ans);
}

int main(){
    read_and_parse();
    solve();
    return 0;   
} 

转载于:https://www.cnblogs.com/wzj-xhjbk/p/10366883.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值