bzoj1977: [BeiJing2010组队]次小生成树 Tree 树上倍增

19 篇文章 0 订阅
5 篇文章 0 订阅

求严格次小生成树
用倍增记录最小值和次小值+求lca,枚举每一条非树边,然后转移更新

PS:dfs时注意最小值和次小值的转移

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
typedef long long ll;
const int N=1e5+10,M=3e5+10;
const int inf=0x7fffffff;
int n,m,len=0,last[N],
f[N][20],mx_1[N][20],mx_2[N][20],dep[N],Fa[N],vis[N];
ll ans=0,mn=inf;
struct Edge{ll from,to,next,val;Edge(ll from=0,ll to=0,ll next=0,ll val=0):from(from),to(to),next(next),val(val){}}e[N<<1];
struct Data{
    ll from,to,val;
    Data(ll from=0,ll to=0,ll val=0):from(from),to(to),val(val){}
    bool operator <(const Data &rhs)const {
        return rhs.val>val;
    }
}E[M];
void add_edge(ll u,ll v,ll w){e[++len]=Edge(u,v,last[u],w);last[u]=len;}
ll find(ll x){return x==Fa[x]?Fa[x]:Fa[x]=find(Fa[x]);}
void Kruskal()
{
    int tot=0;
    fo(i,1,n)Fa[i]=i;
    for(int i=1;i<=m;i++) {
        ll p=find(E[i].from),q=find(E[i].to);
        if(p!=q){
            Fa[p]=q;
            ans+=E[i].val;
            vis[i]=1;
            add_edge(E[i].from,E[i].to,E[i].val);
            add_edge(E[i].to,E[i].from,E[i].val);
            tot++;if(tot==n-1)break;
        }
    }
}
#define d1 mx_1 
#define d2 mx_2
void dfs(int u,int fa)
{
    for(int i=1;i<=16;i++) {
        if(dep[u]<(1<<i))break;
        f[u][i]=f[f[u][i-1]][i-1];
        mx_1[u][i]=max(mx_1[u][i-1],mx_1[f[u][i-1]][i-1]);
        if(mx_1[u][i-1]==mx_1[f[u][i-1]][i-1])
            mx_2[u][i]=max(mx_2[u][i-1],mx_2[f[u][i-1]][i-1]);
        else {
            mx_2[u][i]=min(mx_1[u][i-1],mx_1[f[u][i-1]][i-1]);
            mx_2[u][i]=max(mx_2[u][i],max(mx_2[u][i-1],mx_2[f[u][i-1]][i-1]));
        }
    }
    for(int i=last[u];i;i=e[i].next) {
        int id=e[i].to;
        if(id==fa)continue;
        f[id][0]=u;dep[id]=dep[u]+1;
        mx_1[id][0]=e[i].val;
        dfs(id,u);
    }
}
ll lca(int x,int y)
{
    if(dep[x]<dep[y])swap(x,y);
    int d=dep[x]-dep[y];
    for(int i=0;i<=16;i++) 
        if(d&(1<<i)) {x=f[x][i];}
    for(int i=16;i>=0;i--) 
        if(f[x][i]!=f[y][i]) 
            x=f[x][i],y=f[y][i];

    if(x==y)return x;
    else return f[x][0];
}
void cal(int x,int ff,int v)
{
    int t=dep[x]-dep[ff],mx1=0,mx2=0;
    for(int i=0;i<=16;i++){
        if(t&(1<<i)){
           if(d1[x][i]>mx1){    
               mx2=mx1;
               mx1=d1[x][i];
           }
           mx2=max(mx2,d2[x][i]);
           x=f[x][i];
        }
    }
    if(mx1!=v)mn=min(mn,ll(v-mx1));
    else mn=min(mn,ll(v-mx2));
}
void solve(int t)
{
    int x=E[t].from,y=E[t].to,ff=lca(x,y),v=E[t].val;
    cal(x,ff,v);cal(y,ff,v);
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int u,v,w,i=1;i<=m;i++){ 
        scanf("%d%d%d",&u,&v,&w);
        E[i]=Data(u,v,w);
    }sort(E+1,E+1+m);
    Kruskal();
    dfs(1,0);
    for(int i=1;i<=m;i++) if(!vis[i]) solve(i);
    printf("%lld",ans+mn);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值