次小生成树(lca)

题目描述

原题来自:BeiJing 2010 组队赛

给定一张 N 个点 M 条边的无向图,求无向图的严格次小生成树。

设最小生成树的边权之和为 sum,严格次小生成树就是指边权之和大于 sum 的生成树中最小的一个。

输入格式

第一行包含两个整数 N 和 M,表示无向图的点数与边数;

接下来 M 行,每行三个数 x,y,z表示点 x和点 y 之间有一条边,边的权值为 z。

输出格式

包含一行,仅一个数,表示严格次小生成树的边权和。

数据保证必定存在严格次小生成树。

样例

样例输入

5 6 
1 2 1 
1 3 2 
2 4 3 
3 5 4 
3 4 3 
4 5 6

样例输出

11
#include<bits/stdc++.h>
const int MAXN=100005,MAXM=300005;
using namespace std;
int n,m,N;
struct E{
    int u,v,f;
    long long val;
}a[MAXM*2];
bool mw(E a,E b)
{
    return a.val<b.val;
}
struct Edge{
    int to,next;
    long long val;
}edge[MAXM*2];
int head[MAXN],num=0;
int fa[MAXN],find();
long long ans,mn=100000000000;
int f[MAXN][25],dep[MAXN];
long long d1[MAXN][25],d2[MAXN][25];
inline void add(int u,int v,long long val)
{
    edge[++num].next=head[u];
    edge[num].to=v;
    edge[num].val=val;
    head[u]=num;
    return;
}
inline int find(int a)
{
    return a==fa[a]?a:fa[a]=find(fa[a]);
}
inline void grow()
{
    int u,v,val,cnt=0;
    sort(a+1,a+m+1,mw);
    int r1,r2;
    for(register int i=1;i<=n;++i)fa[i]=i;
    for(register int i=1;i<=m;++i)
    {
        u=a[i].u,v=a[i].v,val=a[i].val;
        r1=find(u);
        r2=find(v);
        if(r1!=r2)
        {
            cnt++;
            a[i].f=1;
            add(u,v,val);
            add(v,u,val);
            fa[r1]=r2;
            ans+=val;
            if(cnt==n-1) break;
        }
    }
    return;
}
inline void dfs(int u)
{
    for(register int i=1;i<=N;i++)
    {
        f[u][i]=f[f[u][i-1]][i-1];
        d1[u][i]=max(d1[u][i-1],d1[f[u][i-1]][i-1]);
        if(d1[u][i-1]==d1[f[u][i-1]][i-1])
            d2[u][i]=max(d2[u][i-1],d2[f[u][i-1]][i-1]);
        else 
        {
            d2[u][i]=min(d1[u][i-1],d1[f[u][i-1]][i-1]);
            d2[u][i]=max(d2[u][i-1],d2[u][i]);
            d2[u][i]=max(d2[u][i],d2[f[u][i-1]][i-1]);
        }
    }
    for(register int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==f[u][0])continue;
        f[v][0]=u;
        d1[v][0]=edge[i].val;
        dep[v]=dep[u]+1;
        dfs(v);
    }
    return;
}
inline int lca(int a,int b)
{
    if(dep[a]>dep[b]) swap(a,b);
    for(register int i=N;i>=0;i--)
    {
        if(dep[a]<dep[b]&&dep[f[b][i]]>=dep[a]) b=f[b][i];
    }
    if(a==b)return a;
    for(register int i=N;i>=0;i--)
    {
        if(f[a][i]!=f[b][i]){a=f[a][i],b=f[b][i];}
    }
    return f[a][0];
}
inline void Try(int u,int father,long long val)
{
    int t;
    t=dep[u]-dep[father];
    long long mx1=-1,mx2=0;
    for(int i=0;i<=N;i++)
    {
        if(t&(1<<i))//注意这一步!! 
        {
            if(mx1<=d1[u][i])
            {
                mx2=mx1;
                mx1=d1[u][i];
                mx2=max(mx2,d2[u][i]);
            }
            else
            {
                mx2=max(mx2,d1[u][i]);
            }
            u=f[u][i];//写的好神奇 
        }
    }
    if(mx1!=val)
    {
        if(mn>val-mx1)
        {
            mn=val-mx1;
        }
    }
    else
    {
        if(mn>val-mx2)
        {
            mn=val-mx2;
        }
    }
    return;
}
inline void choose()
{
    int f,u,v,val;
    for(register int i=1;i<=m;++i)
    {
        if(a[i].f==1)
          continue;
        u=a[i].u;
        v=a[i].v;
        val=a[i].val;
        f=lca(u,v);
        Try(u,f,val),Try(v,f,val);
    }
    return;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(register int i=1;i<=m;++i)
    {
        scanf("%d%d%lld",&a[i].u,&a[i].v,&a[i].val);
    }
    grow();
    N=floor(log(n+0.0)/log(2.0));
    dfs(1);
    choose();
    printf("%lld",ans+mn);
    return 0;
}

 

posted @ 2018-09-04 19:36  南柯一场  阅读(327)  评论(0编辑  收藏  举报