次小生成树

次小生成树的定义:

    次小生成树根据名字就知道是比最小生成树的权值和还要大的生成树,而且是大于最小生成树的权值和的权值最小的那个生成树。

 

次小生成树的求法:

 1.暴力拆边法

  由最小生成树可得最小生成树中的边的权值和最小,那么我们可以每次考虑枚举删除其中的边,并用其他的权值尽量小的非树边顶上,对新的生成树的权值和取个min就行了,时间复杂度:O(nmlogm)。

  #10068. 「一本通 3.1 练习 3」秘密的牛奶运输:https://loj.ac/problem/10068

 

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
#define INF 0x3f3f3f3f
#define maxn  509    
#define maxm 10009
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ll)(ch-'0');ch=getchar();}
    return x*f;
}
struct edge
{
    int x,y;
    ll val;
}p[maxm]; 
int n,m,k,tot,cnt;
int fa[maxn],vis[maxn];
ll ans,mx,sum;
bool comp(edge a,edge b)
{
    return a.val<b.val;
}

int find(int x)
{
    return fa[x]==x?fa[x]:fa[x]=find(fa[x]);
}
int main()
{
//    freopen(".in","r",stdin);
//    freopen(".out","w",stdout);
    n=read(),m=read();
    for(int i=1;i<=n;i++)
        fa[i]=i;
    for(int i=1;i<=m;i++)
        p[i].x=read(),p[i].y=read(),p[i].val=read();
    sort(p+1,p+1+m,comp);
    for(int i=1;i<=m;i++)
    {
        int a=find(p[i].x),b=find(p[i].y);
        if(a!=b)
        {
            fa[b]=a;
            ans+=p[i].val;
            vis[++cnt]=i;
            if(cnt==n-1)
                break;
        }
    }
    mx=1e18;
    for(int i=1;i<=cnt;i++)
    {    
        sum=0,tot=0;
        for(int j=1;j<=n;j++)
            fa[j]=j;
        for(int j=1;j<=m;j++)
        {
            if(vis[i]==j)
                continue;
            int a=find(p[j].x),b=find(p[j].y);
            if(a!=b)
            {
                sum+=p[j].val;
                fa[b]=a;
                tot++;
                if(tot==n-1)
                    break;
            }
        }
        if(sum>ans)
        mx=min(mx,sum);
    }
    if(mx==1e18)
        mx=10;
    printf("%lld\n",mx);
    fclose(stdin);
    fclose(stdout);
    return 0;
}

 

   2.倍增

  考虑在最小生成树中加一条非树边(u,v),那么整棵树将会形成一个环,去掉环上的任意一条边后仍然为一颗生成树,因为我们所需要求的是次小生成树,所以可以贪心地将环上树边中权值最大的边删除并加上边(u,v)。

  实现:①先用Kruskal求出最小生成树并标记树边和最小生成树的权值和。

     ②用dfs求出f数组和每个节点的深度。

     ③用倍增的方法求出最小生成树中任意两点间路径的最大和次大值。

      ④枚举每一条非树边(u,v),利用LCA求出环上的树边的最大值,将其权值减去并加上树边(u,v)的权值,更新答案。

  求次大值是因为所求为严格的次小生成树,其权值和要大于最小生成树的权值和,当所枚举的非树边的边权和环上树边的最大值相同时便取次大值。

  因为取最大值后所形成的心得生成树的权值和与原来的最小生成树的权值和必然相等,并不满足题目要求。

   BZOJ 1977 次小生成树 Tree:https://www.lydsy.com/JudgeOnline/problem.php?id=1977

  

#include<bits/stdc++.h>
using namespace std;
#define re register int
#define ll long long
#define INF 0x3f3f3f3f 
#define maxn 100009
#define maxm 300009
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ll)(ch-'0');ch=getchar();}
    return x*f;
}
struct EDGE
{
    int x,y;
    ll val;
}pp[maxm];
struct edge
{
    int to,nxt;
    ll val;
}p[maxn<<1];
bool used[maxm];
int head[maxn],depth[maxn],f[maxn][29],fa[maxn];
ll mx[maxn][29],cmx[maxn][29];
int n,m,k,tot,cnt;
ll ans,sum;
//f[i][j]表示节点i往父节点方向走2^j步后的节点编号 
//mx[i][j]      ...                  间的路径上边的最大值 
//cmx[i][j]     ...                                次大值     
void add(int x,int y,ll z)
{
    ++cnt,p[cnt].to=y,p[cnt].nxt=head[x],head[x]=cnt,p[cnt].val=z;
}

bool comp(EDGE a,EDGE b)
{
    return a.val<b.val;
}

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

void Kruskal()//求最小生成树 
{
    sort(pp+1,pp+1+m,comp);
    for(int i=1;i<=m;i++)
    {
        int a=find(pp[i].x),b=find(pp[i].y);
        if(b!=a)
        {
            fa[b]=a;
            used[i]=1;//used[i]=1表示编号为i的这条边是树边 
            ans+=pp[i].val;
            tot++;    
            add(pp[i].x,pp[i].y,pp[i].val);//建最小生成树 
            add(pp[i].y,pp[i].x,pp[i].val);
            if(tot==n-1)
                break;
        }
    }
}

void dfs(int u,int father)//求f数组和depth数组 每个点的深度 
{
    depth[u]=depth[father]+1;
    f[u][0]=father;
    for(int i=1;(1<<i)<=depth[u];i++)
        f[u][i]=f[f[u][i-1]][i-1];
    for(int i=head[u];i;i=p[i].nxt)
    {
        int v=p[i].to;
        if(v==father)
            continue;
        mx[v][0]=p[i].val;
        dfs(v,u);
    }
}

void Cal()//倍增求出mx[]和cmx[] 
{
    for(int j=1;j<=20;j++)
        for(int i=1;i<=n;i++)
        {
            mx[i][j]=max(mx[i][j-1],mx[f[i][j-1]][j-1]);//i往上跳2^j步路径上的最大值为 i往上跳2^(j-1)路径上的最大值 
            //和以i往上跳2^(j-1)步所到达的节点为起点跳2^(j-1)步路径上的最大值 
            cmx[i][j]=max(cmx[i][j-1],cmx[f[i][j-1]][j-1]);//次大值同最大值 
            if(cmx[i][j]<mx[i][j-1]&&mx[i][j-1]<mx[f[i][j-1]][j-1])//将最大值中较小的值和次大值进行比较更新 
                cmx[i][j]=mx[i][j-1];
            else if(cmx[i][j]<mx[f[i][j-1]][j-1]&&mx[f[i][j-1]][j-1]<mx[i][j-1])
                cmx[i][j]=mx[f[i][j-1]][j-1];
        }
}

int Lca(int x,int y)
{
    if(depth[x]>depth[y])
        swap(x,y);
    for(int i=20;i>=0;i--)
        if(depth[x]<=depth[y]-(1<<i))
            y=f[y][i];
    if(x==y)
        return x;
    for(int i=20;i>=0;i--)
        if(f[x][i]!=f[y][i])
            x=f[x][i],y=f[y][i];
    return f[x][0];
}

ll Query_mx(int x,int y,int lca,ll val)//倍增找(u,v)路径上的最大值,分为u->lca 和 v->lca  
{
    ll lmx=0,rmx=0;
    for(int i=20;i>=0;i--)
        if(depth[lca]<=depth[x]-(1<<i))
        {
            if(mx[x][i]!=val)
                lmx=max(lmx,mx[x][i]);
            else
                lmx=max(lmx,cmx[x][i]);
            x=f[x][i];
        }
    for(int i=20;i>=0;i--)
        if(depth[lca]<=depth[y]-(1<<i))
        {
            if(mx[y][i]!=val)
                rmx=max(rmx,mx[y][i]);
            else
                rmx=max(rmx,cmx[y][i]);
            y=f[y][i];
        }
    return max(lmx,rmx);
}

void Get_ans()//枚举非树边 
{
    sum=2e18;
    for(int i=1;i<=m;i++)
        if(!used[i])
        {
            int x=pp[i].x,y=pp[i].y;
            ll z=pp[i].val;
            int father=Lca(x,y);
            ll temp=Query_mx(x,y,father,z);
            if(temp!=z&&ans-temp+z<sum)
                sum=ans-temp+z; 
        }
    printf("%lld\n",sum);
}

int main()
{
//    freopen(".in","r",stdin);
//    freopen(".out","w",stdout);
    n=read(),m=read();
    for(int i=1;i<=n;i++)
        fa[i]=i;
    for(int i=1;i<=m;i++)
        pp[i].x=read(),pp[i].y=read(),pp[i].val=read();
    Kruskal();
    dfs(1,0);
    Cal();
    Get_ans();
    fclose(stdin);
    fclose(stdout);
    return 0;
}

 

转载于:https://www.cnblogs.com/Dxy0310/p/9769757.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值