• 次小生成树


    次小生成树的定义:

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

    次小生成树的求法:

     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
    ",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
    ",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;
    }
  • 相关阅读:
    【JSP】上传图片到数据库中
    【MySQL】ERROR 1005: Can't create table (errno: 150)的错误解决办法
    【汇编】16进制转换成10进制(三种方法)
    书单
    C实现一个NTP客户端,可以从指定IP的NTP服务器获取时间戳
    crosss compile VLC with OpenMAX on ARM board(RockChip RK3399),in order to use Hard Acceleration when decode video
    win7结束进程 时,提示“拒绝访问”、“没有此任务的实例运行”怎么办?
    驱动文件中只有cat/inf/dll文件,怎么安装
    MFC开发中添加自定义消息和消息响应函数
    MFC中开发ocx控件,html容器收不到ocx的事件Event
  • 原文地址:https://www.cnblogs.com/Dxy0310/p/9769757.html
Copyright © 2020-2023  润新知