【BZOJ】1977 [BeiJing2010组队]次小生成树 Tree kruskal+LCA

110 篇文章 0 订阅
10 篇文章 0 订阅

题目传送门

这题好像在好久以前就讲过啊……为什么我现在才去做……

其实正解挺好想的,首先做一棵最小生成树,然后枚举一条非树边,显然这条非树边的加入可以使原本树上的唯一路径形成一个环,把这个环上严格第二小的边删掉,就是一棵严格次小生成树了。然后对所有的次小生成树求一个最小权值即可。

至于怎么查找原来的唯一路径上的严格次大值,可以用ST表维护最大值和次大值。

附上AC代码:

#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;

typedef long long ll;
const int M=3e5+10,N=1e5+10;
struct note{
    int x,y,w;
    bool operator < (const note lyf) const {return w<lyf.w;}
}a[M];
struct side{
    int to,w,nt;
}s[N<<1];
int n,m,fa[N],h[N],num,d[N],f[N][20],mx1[N][20],mx2[N][20],mn;
ll ans;
bool b[M];

inline char nc(void){
    static char ch[100010],*p1=ch,*p2=ch;
    return p1==p2&&(p2=(p1=ch)+fread(ch,1,100010,stdin),p1==p2)?EOF:*p1++;
}

inline void read(int &a){
    static char c=nc();int f=1;
    for (;!isdigit(c);c=nc()) if (c=='-') f=-1;
    for (a=0;isdigit(c);a=(a<<3)+(a<<1)+c-'0',c=nc());
    return (void)(a*=f);
}

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

inline void add(int x,int y,int w){
    s[++num]=(side){y,w,h[x]},h[x]=num;
    s[++num]=(side){x,w,h[y]},h[y]=num;
}

inline void so(int x,int fa){
    for (int i=1; i<=17; ++i){
        if (d[x]<(1<<i)) break;
        f[x][i]=f[f[x][i-1]][i-1];
        mx1[x][i]=max(mx1[x][i-1],mx1[f[x][i-1]][i-1]);
        if (mx1[x][i-1]==mx1[f[x][i-1]][i-1]) mx2[x][i]=max(mx2[x][i-1],mx2[f[x][i-1]][i-1]);
        else {
            mx2[x][i]=min(mx1[x][i-1],mx1[f[x][i-1]][i-1]);
            mx2[x][i]=max(mx2[x][i],max(mx2[x][i-1],mx2[f[x][i-1]][i-1]));
        }
    }
    for (int i=h[x]; i; i=s[i].nt)
        if (s[i].to!=fa) d[s[i].to]=d[f[s[i].to][0]=x]+1,mx1[s[i].to][0]=s[i].w,so(s[i].to,x);
}

inline int lca(int x,int y){
    if (d[x]<d[y]) swap(x,y);int t=d[x]-d[y];
    for (int i=0; i<=17; ++i) if (t&(1<<i)) x=f[x][i];
    for (int i=17; i>=0; --i) if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    if (x==y) return x;
    return f[x][0];
}

inline void calc(int x,int fa,int w){
    int d1=0,d2=0,t=d[x]-d[fa];
    for (int i=0; i<=17; ++i)
        if (t&(1<<i)){
            if (mx1[x][i]>d1) d2=d1,d1=mx1[x][i];
            d2=max(d2,mx2[x][i]),x=f[x][i];
        }
    if (d1!=w) mn=min(mn,w-d1); else mn=min(mn,w-d2);
    return;
}

int main(void){
    read(n),read(m);
    for (int i=1; i<=m; ++i) read(a[i].x),read(a[i].y),read(a[i].w);
    sort(a+1,a+1+m);
    for (int i=1; i<=n; ++i) fa[i]=i;
    for (int i=1; i<=m; ++i){
        int fx=gf(a[i].x),fy=gf(a[i].y);
        if (fx!=fy) fa[fx]=fy,ans+=a[i].w,b[i]=1,add(a[i].x,a[i].y,a[i].w);
    }
    so(1,0),mn=2e9;
    for (int i=1; i<=m; ++i)
        if (!b[i]){
            int t=lca(a[i].x,a[i].y);
            calc(a[i].x,t,a[i].w),calc(a[i].y,t,a[i].w);
        }
    return printf("%lld\n",ans+mn),0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值