码迷,mamicode.com
首页 > 其他好文 > 详细

luogu P4180 【模板】严格次小生成树[BJWC2010]

时间:2019-08-08 21:19:58      阅读:101      评论:0      收藏:0      [点我收藏+]

标签:更新   节点   ==   line   space   int   最小生成树   logs   scan   

思路:

首先要想清楚一个问题,对于次小生成树,肯定是在最小生成树上断掉一条边,然后在非最小生成树边中加一条边进去产生的(具体窝也不会证鸭;知道了这个非常显然的结论后具体思路就好想了.

做法:

我们考虑暴力删边和加边.对于每条未在最小生成树中的边,考虑删掉最小生成树中的一条边(边权尽可能大)并加入此边,因为非最小生成树的边权肯定都大于等于最小生成树边的边权,所以删掉的边边权越大,新树的总边权才会越小,并且要保证新树还是生成树.

所以我们先求出最小生成树记录下总边权值,然后对于每一条非最小生成树边的两个节点在最小生成树上求一次\(lca\)(保证新树是生成树),然后求出节点到\(lca\)的最大边权和次大边权(因为最大边权可能和要加进去的边边权相等不满足严格次小生成树,所以要多求一个次小边权).然后用总边权减去求得最大边权(或次大边权)再加上要加进去的边的边权,记录答案最小值即可.

// luogu-judger-enable-o2
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=100000+5;
const int M=300000+5;
const int logs=19;
const int INF=2147483647*1e9;
int n,m,tot,cnt,sum;
int head[N],dep[N],pre[N][logs+1],vis[M],fa[N],zd[N][logs+1],cd[N][logs+1];
struct Edge{
    int next,to,dis;
}e[M*2];
struct node{
    int u,v,w;
}a[M*2];
int find(int x){return x==fa[x] ? fa[x] : fa[x]=find(fa[x]);}
inline void merge(int x,int y){fa[x]=y;}
inline void add_edge(int from,int to,int dis){
    e[++tot].next=head[from];
    e[tot].to=to;
    e[tot].dis=dis;
    head[from]=tot;
}
bool cmp(node a,node b){return a.w<b.w;}
void dfs(int now,int faa){
    pre[now][0]=faa;
    dep[now]=dep[faa]+1;
    for(int i=1;i<=logs;i++){
        pre[now][i]=pre[pre[now][i-1]][i-1];
        zd[now][i]=max(zd[now][i-1],zd[pre[now][i-1]][i-1]);//最大值
        cd[now][i]=max(cd[now][i-1],cd[pre[now][i-1]][i-1]);//次大值
        if(zd[now][i-1]>zd[pre[now][i-1]][i-1]) cd[now][i]=max(cd[now][i],zd[pre[now][i-1]][i-1]);//这里次大值可能与两个最大值中较小的那个更新
        else if(zd[now][i-1]<zd[pre[now][i-1]][i-1]) cd[now][i]=max(cd[now][i],zd[now][i-1]);
    }//倍增处理祖先,最大值,次大值.
    for(int i=head[now];i;i=e[i].next)
        if(e[i].to!=faa) dfs(e[i].to,now);
}
void cx(int x,int faa){
    for(int i=head[x];i;i=e[i].next){
        int v=e[i].to;
        if(v==faa) continue;
        zd[v][0]=e[i].dis;
        cd[v][0]=-INF;
        cx(v,x);
    }
}//预处理最大值次大值数组
inline int lca(int x,int y,int w){
    int ans=-INF;
    if(dep[x]<dep[y]) std::swap(x,y);
    for(int i=logs;i>=0;i--)
        if(dep[pre[x][i]]>=dep[y]){
            if(zd[x][i]==w) ans=max(ans,cd[x][i]);
            else ans=max(ans,zd[x][i]);
            x=pre[x][i];
        }
    if(x==y) return ans;
    for(int i=logs;i>=0;i--)
        if(pre[x][i]!=pre[y][i]){
            if(w==zd[x][i]) ans=max(ans,cd[x][i]);
            else ans=max(ans,zd[x][i]);
            if(w==zd[y][i]) ans=max(ans,cd[y][i]);
            else ans=max(ans,zd[y][i]);
            x=pre[x][i],y=pre[y][i];
        }
    if(w==zd[x][0]) ans=max(ans,cd[x][0]);
    else ans=max(ans,zd[x][0]);
    if(w==zd[y][0]) ans=max(ans,cd[y][0]);
    else ans=max(ans,zd[y][0]);
    return ans;
}//这里直接在求lca的过程中把最大值和次大值求出来.
signed main(){
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=m;i++) scanf("%lld%lld%lld",&a[i].u,&a[i].v,&a[i].w);
    sort(a+1,a+1+m,cmp);
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=m;i++){
        int x=find(a[i].u),y=find(a[i].v);
        if(x!=y){
            ++cnt;
            merge(x,y);
            sum+=a[i].w;
            add_edge(a[i].u,a[i].v,a[i].w);
            add_edge(a[i].v,a[i].u,a[i].w);
            vis[i]=1;//标记最小生成树边
            if(cnt==n-1) break;
        }
    }//最小生成树
    cx(1,0);
    dfs(1,0);//预处理
    int ans=INF;
    for(int i=1;i<=m;i++){
        if(!vis[i]){
            int k=lca(a[i].u,a[i].v,a[i].w);
            ans=min(ans,sum-k+a[i].w);
        }
    }
    printf("%lld",ans);
    return 0;
}

luogu P4180 【模板】严格次小生成树[BJWC2010]

标签:更新   节点   ==   line   space   int   最小生成树   logs   scan   

原文地址:https://www.cnblogs.com/cnyali-xwx/p/11323655.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!