朱刘算法

时间:2019-08-21
本文章向大家介绍朱刘算法,主要包括朱刘算法使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

模板题传送门

一个有向图,存在从某个点为根的,可以到达所有点的一个最小生成树,则它就是最小树形图。

朱刘算法用来求最小树形图,复杂度O(nm)。

大概思想就是缩点+贪心加边。

如果图中存在环,就把环缩成一个点,一直到没有环为止。

考虑正确性:

最小树形图和最小生成树的区别就在与有方向,对于一个环,我们可以选择一条入边+所有环边-1(-1是因为有一条入边可以去掉一条环边)

而对于每一个点,我们用一个minn数组存下了与它直接相连的点到它的最小边,ans等于所有minn的累加,那么一定是最优解。

用while一直执行缩点过程。

注意:vis用来避免一直走环。

具体流程和解释代码中给出(借鉴洛谷题解)

#include<bits/stdc++.h>
#define N 103
#define M 10002
#define INF 210000001
#define LL long long
using namespace std;
int read()
{
    int x=0,f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    return x*f;
}
struct EDGE{
    int a,b,v;
}w[M];
int minn[N],id[N],vis[N],fa[N];
int cnt=0;
//cnt当前图环的数量 
//id[u]代表u节点在第id[u]个环中
//vis[u]打标记避免一直走环 
//minn[u]为当前连到u点的最短边的边权 fa[v]当前连到v点的最短边的u
LL ans=0;
int zhuliu(int n,int m,int r)
{
    while(1)
    {
        for(int i=1;i<=n;++i) minn[i]=INF,id[i]=vis[i]=0;
        for(int i=1;i<=m;++i)
        {
            if(w[i].a!=w[i].b&&w[i].v<minn[w[i].b])//不是自环 并且边权比选定的还小 
              fa[w[i].b]=w[i].a,minn[w[i].b]=w[i].v;
        }
        int u;
         minn[r]=0;
        for(int i=1;i<=n;++i)
        {
            if(minn[i]==INF)return 0;//存在一个不可以连接的点,什么不能找到最小树形图 
            ans+=minn[i];//这里就会更新到ans中 
            for(u=i;u!=r&&!id[u]&&vis[u]!=i;u=fa[u])vis[u]=i;//打上标记,这里走过的是一条链,vis打上i而不是1,因为可能多条边指向一个点 
            if(u!=r&&!id[u])//没有走到根,找到一个新环 
            {
                 id[u]=++cnt;
                for(int v=fa[u];v!=u;v=fa[v])id[v]=cnt;
             }
         }
        if(!cnt)return 1;//没有环了,说明现在就是最小树形图,边权和在上面就已经加入ans了 
        for(int i=1;i<=n;++i)
          if(!id[i])id[i]=++cnt;//i节点不存在当前树中 就给他自己成一个环 
        for(int i=1;i<=m;++i)
        {
               int last=minn[w[i].b];//last等于当前连进v点的边的最小权值 
            if((w[i].a=id[w[i].a])!=(w[i].b=id[w[i].b]))w[i].v-=last;
            //缩环的时候记得加入答案,环上的出边直接接上,入边要注意,选一条入边相当于删掉一条环边
            //当前边的两个端点不在同一个环内
        }
        n=cnt;cnt=0;r=id[r];//缩完点后 当前点数就为环数 根节点就是根节点所在的环
    }
}
int main()
{
    int n=read(),m=read(),r=read();
    for(int i=1;i<=m;++i)
      w[i].a=read(),w[i].b=read(),w[i].v=read();
    if(zhuliu(n,m,r))printf("%lld\n",ans);
    else printf("-1\n");
}
注释版

再放一个没有注释的

#include<bits/stdc++.h>
#define N 103
#define M 10002
#define INF 210000001
#define LL long long
using namespace std;
int read()
{
    int x=0,f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    return x*f;
}
struct EDGE{
    int a,b,v;
}w[M];
int minn[N],id[N],vis[N],fa[N];
int cnt=0;
LL ans=0;
int zhuliu(int n,int m,int r)
{
    while(1)
    {
        for(int i=1;i<=n;++i) minn[i]=INF,id[i]=vis[i]=0;
        for(int i=1;i<=m;++i)
        {
            if(w[i].a!=w[i].b&&w[i].v<minn[w[i].b])
              fa[w[i].b]=w[i].a,minn[w[i].b]=w[i].v;
        }
        int u;
         minn[r]=0;
        for(int i=1;i<=n;++i)
        {
            if(minn[i]==INF)return 0;
            ans+=minn[i];
            for(u=i;u!=r&&!id[u]&&vis[u]!=i;u=fa[u])vis[u]=i;
            if(u!=r&&!id[u])
            {
                 id[u]=++cnt;
                for(int v=fa[u];v!=u;v=fa[v])id[v]=cnt;
             }
         }
        if(!cnt)return 1;
        for(int i=1;i<=n;++i)
          if(!id[i])id[i]=++cnt;
        for(int i=1;i<=m;++i)
        {
               int last=minn[w[i].b];
            if((w[i].a=id[w[i].a])!=(w[i].b=id[w[i].b]))w[i].v-=last;
        }
        n=cnt;cnt=0;r=id[r];
    }
}
int main()
{
    int n=read(),m=read(),r=read();
    for(int i=1;i<=m;++i)
      w[i].a=read(),w[i].b=read(),w[i].v=read();
    if(zhuliu(n,m,r))printf("%lld\n",ans);
    else printf("-1\n");
}
无注释版

原文地址:https://www.cnblogs.com/yyys-/p/11348556.html