BZOJ 1977 浅谈严格次小生成树

这里写图片描述
世界真的很大
最小生成树的算法都知道了
但是最小生成树的原理和方法还是需要详细了解的,因为就如这道题一样会有很多变种
比如之前这道题:CodeForces 827D
最小生成树选边的原理要牢记于心

看题先:

description

C 最近学了很多最小生成树的算法,Prim 算法、Kurskal 算法、消圈算法等等。 正当小 C 洋洋得意之时,小 P 又来泼小 C 冷水了。小 P 说,让小 C 求出一个无向图的次小生成树,而且这个次小生成树还得是严格次小的,也就是说: 如果最小生成树选择的边集是 EM,严格次小生成树选择的边集是 ES,那么需要满足:(value(e) 表示边 e的权值)  这下小 C 蒙了,他找到了你,希望你帮他解决这个问题。

input

第一行包含两个整数N 和M,表示无向图的点数与边数。 接下来 M行,每行 3个数x y z 表示,点 x 和点y之间有一条边,边的权值为z。

output

包含一行,仅一个数,表示严格次小生成树的边权和。(数据保证必定存在严格次小生成树)

首先分析题意
由于当时几乎是秒想题解的,现在总结起来反而有些困难233
考虑由于是次小生成树,离最小生成树是非常接近的,所以他们的结构几乎差不多
就是用一条稍微大一点的边去替代最小生成树上的某条边
具体来说,就是对于所有没有被选到的边,其连接的两点在最小生成树上的链上和这条边的权值相减的最小值
由于是最小生成树,所以这条链上的所有边权都会比这条边要小,所以我们只需要在最小生成树的这条链上找一个最大值就行了
这一步我们可以用lca跳倍增时顺便维护

特别的,如果这条链上的最大值正好等于这条边的权值,由于是严格次小,而不是最小生成树的另一种结构,所以这样是不合法的
于是乎我们在维护最大值时还需要维护链上的次大值,如果最大值与边的边权相等,就用次大值去相减

完整代码:

#include<stdio.h>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
typedef long long dnt;

const int INF=0x3f3f3f3f;

struct edge
{
    int u,v,w,last;
}ed[2000010],sid[2000010];

int n,m,num=0,tot=0,ans=INF;
int head[100010],fa[100010],dep[100010];
int anc[100010][20],st[100010][20],sd[100010][20],used[300010];
dnt bns=0;

bool cmp(const edge &a,const edge &b)
{
    return a.w<b.w;
}

void add(int u,int v,int w)
{
    num++;
    ed[num].v=v;
    ed[num].w=w;
    ed[num].last=head[u];
    head[u]=num;
}

int getfather(int x)
{
    if(x==fa[x]) return x;
    return fa[x]=getfather(fa[x]);
}

void Kruscal()
{
    sort(sid+1,sid+m+1,cmp);
    for(int i=1;i<=n;i++)
        fa[i]=i;
    for(int i=1;i<=m;i++)
    {
        int x=getfather(sid[i].u),y=getfather(sid[i].v);
        if(x!=y)
        {
            used[i]=1;
            tot++,bns+=sid[i].w;
            add(sid[i].u,sid[i].v,sid[i].w);
            add(sid[i].v,sid[i].u,sid[i].w);
            fa[x]=y;
        }
        if(tot==n-1) return ;
    }
    return ;
}

void dfs(int u,int f)
{
    anc[u][0]=f;
    for(int p=1;p<=16;p++)
    {
        anc[u][p]=anc[anc[u][p-1]][p-1],
        st[u][p]=max(st[u][p-1],st[anc[u][p-1]][p-1]);
        if(st[u][p-1]==st[anc[u][p-1]][p-1])
            sd[u][p]=max(sd[u][p-1],sd[anc[u][p-1]][p-1]);
        else
        {
            sd[u][p]=min(st[u][p-1],st[anc[u][p-1]][p-1]);
            sd[u][p]=max(sd[u][p-1],sd[u][p]);
            sd[u][p]=max(sd[u][p],sd[anc[u][p-1]][p-1]);
        }
    }
    for(int i=head[u];i;i=ed[i].last)
    {
        int v=ed[i].v;
        if(v==f) continue ;
        dep[v]=dep[u]+1;
        st[v][0]=ed[i].w;
        dfs(v,u);
    }
}

void calmx(int wth,int vth,int &d,int &e)
{
    if(wth==d) return ;
    if(wth>d) e=d,d=wth;
    else if(wth>e) e=wth;
    e=max(e,vth);
}

int lca(int u,int v,int xx)
{
    int d=0,e=0;
    if(dep[u]<dep[v]) swap(u,v);
    for(int i=16;i>=0;i--)
        if(dep[anc[u][i]]>=dep[v])
        {
            calmx(st[u][i],sd[u][i],d,e);
            u=anc[u][i];
        }
    if(u==v)
    {
        if(xx==d) return e;
        return d;
    } 
    for(int i=16;i>=0;i--)
        if(anc[u][i]!=anc[v][i])
        {
            calmx(st[u][i],sd[u][i],d,e);calmx(st[v][i],sd[u][i],d,e);
            u=anc[u][i],v=anc[v][i];
        }
    calmx(st[u][0],sd[u][0],d,e),calmx(st[v][0],sd[v][0],d,e);
    if(xx==d) return e;
    return d;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
        scanf("%d%d%d",&sid[i].u,&sid[i].v,&sid[i].w);
    Kruscal();
    dfs(1,1);
    for(int i=1;i<=m;i++)
    {
        if(used[i]) continue ;
        int u=sid[i].u,v=sid[i].v;
        int tmp=lca(u,v,sid[i].w);
        ans=min(ans,sid[i].w-tmp);
    }
    cout << ans+bns <<endl ;
    return 0;
}
/*
EL PSY CONGROO
*/

嗯,就是这样

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值