次小生成树—学习笔记

次小生成树

分为非严格次小生成树严格次小生成树

对于前者,若最小生成树不唯一
则次小生成树与最小生成树权值相同

对于后者,则要求次小生成树权值严格大于最小生成树

接下来的求解方法都将分别讨论

这里是次小生成树的版题

洛谷P4180 严格次小生成树[BJWC2010]


算法一:

这一算法较简洁易懂,且代码量小
但算法时间复杂度较高,一般不建议用,了解思路即可
效率较高的算法参见算法二


首先,不难证明次小生成树的连边与最小生成树一定不相同
因此,我们可以枚举每一条在最小生成树上的边
在剩下的边的集合中再求最小生成树
也就是再对n-1个缺一条边的图求最小生成树

对于严格次小生成树
找出n-1棵树中找到权值>原最小生成树且最小的

对非严格次小生成树
找出n-1棵树中找到权值>=原最小生成树且最小的

这种思路可以说是很暴力了
代码就不贴了,主要讲下面的高效算法



算法二:

这一算法代码量较大,维护方法不唯一
思路不难,主要在于维护方法的选择,特别是对于严格次小生成树
算法时间复杂度是很高效的(也要看维护方法)


对于一棵已经求出的最小生成树
枚举每一条不在最小生成树上的边
并把这条边加入最小生成树

设这条边连接的两个节点为u和v
这时树上u到v的路径会出现回路
所以我们需要删掉u到v路径上的一条边使其重新变成一颗树

这时生成树权值的增量就是新加入的边权-删掉的边权
为了使增量最小,我们要删去的自然是u到v路径中权值最大的边

特别的,若要求的是严格次小生成树
如果发现u到v路径中权值最大的边等于加入的边
就要删掉u到v路径上的次大边权

对于树上路径权值的维护选择有很多
例如树剖、LCT、倍增等等
这里用倍增示例


#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#include<cstring>
using namespace std;
typedef long long lt;

lt read()
{
    lt f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const int maxn=100010;
lt n,m,ans;
struct node{int u,v;lt dis;}edge[maxn*10];
struct node2{lt v,dis,nxt;}E[maxn*10];
int head[maxn],tot;
int fa[maxn];
bool judge[maxn];//判断是否是最小生成树的边
int gra[maxn][18],dep[maxn];
lt fir[maxn][18],sec[maxn][18];
//fir表示区间内最大值,sec表示次大值,和lca一样的倍增思想

bool cmp(node a,node b){return a.dis<b.dis;}

void add(int u,int v,lt dis)
{
    E[++tot].nxt=head[u];
    E[tot].v=v;
    E[tot].dis=dis;
    head[u]=tot; 
}

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

lt kruskal()
{
    int num=0; lt len=0;
    for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=1;i<=m;i++)
    {
        int u=edge[i].u,v=edge[i].v;lt dis=edge[i].dis;
        int fu=find(u),fv=find(v);
        if(fu!=fv)
        {
            fa[fu]=fv;  judge[i]=true;
            num++;  len+=dis; 
            add(u,v,dis); add(v,u,dis);
            if(num==n-1) break; 
        } 
    }
    return len;
}

void dfs(int u,int pa)
{
    for(int i=head[u];i;i=E[i].nxt)
    {
        int v=E[i].v;
        if(v==pa) continue;
        dep[v]=dep[u]+1; gra[v][0]=u; 
        fir[v][0]=E[i].dis; sec[v][0]=-1e9;
        dfs(v,u);
    }
}

void work()
{
    for(int i=1;(1<<i)<=n;i++)
    for(int u=1;u<=n;u++)
    {
        gra[u][i]=gra[ gra[u][i-1] ][i-1];//处理祖先
        fir[u][i]=max(fir[u][i-1],fir[ gra[u][i-1] ][i-1]);
        sec[u][i]=max(sec[u][i-1],sec[ gra[u][i-1] ][i-1]);
        //处理最大和次大值
        if(fir[u][i-1]>fir[ gra[u][i-1] ][i-1])
        sec[u][i]=max(fir[ gra[u][i-1] ][i-1],sec[u][i]);
        else if(fir[u][i-1]<fir[ gra[u][i-1] ][i-1])
        sec[u][i]=max(fir[u][i-1],sec[u][i]);
        //注意对次大值的判断
    }
}

int lca(int u,int v)
{
    if(dep[u]<dep[v]) swap(u,v);
    int d=dep[u]-dep[v];
    
    for(int i=0;(1<<i)<=d;i++)
    if((1<<i)&d) u=gra[u][i];
    
    if(u==v) return u;
    for(int i=(int)log(n);i>=0;i--)
    {
        if(gra[u][i]!=gra[v][i])
        u=gra[u][i],v=gra[v][i];
    }
    return gra[u][0];
}

lt qmax(int u,int v,lt dis)
{
    lt tp=-1e9;
    for(int i=1;(1<<i)<=n;i++)//倍增思想
    {
        //只要深度比LCA大,就倍增查询更新
        if(dep[ gra[u][i] ]>=dep[v])
        {
            if(dis==fir[u][i]) tp=max(tp,sec[u][i]);
            else tp=max(tp,fir[u][i]);//发现一样的边权要查询次大值
        }
    }
    return tp;
}

int main()
{
    n=read();m=read();
    for(int i=1;i<=m;i++)
    edge[i].u=read(),edge[i].v=read(),edge[i].dis=read();
    
    sort(edge+1,edge+1+m,cmp);
    ans=kruskal();//先求最小生成树

    dfs(1,-1);//无根树转有根树
    work();//预处理gra,fir和sec
    
    lt add=1e9;
    for(int i=1;i<=m;i++)
    {
        if(judge[i])continue;//若是最小生成树的边就跳过
        int u=edge[i].u,v=edge[i].v;lt dis=edge[i].dis;
        int LCA=lca(u,v);
        lt maxu=qmax(u,LCA,dis);
        lt maxv=qmax(v,LCA,dis);//分别查询u、v到他们LCA路径上的最大值
        add=min(add,dis-max(maxu,maxv));//更新最小增量
    }
    
    cout<<ans+add;
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值