学习笔记:朱刘算法

这篇博客介绍了朱刘算法,用于求解有向图中的最小树形图问题。首先解释了树形图的概念,即一种没有环且除根节点外点的入度为1的有向图。接着详细阐述了朱刘算法的步骤,包括寻找每个点的最小前驱边、检查环的存在以及如何更新边权和缩点。通过示例展示了算法的实现,并指出无解的情况。最后,提供了一段C++代码来解决这个问题。
摘要由CSDN通过智能技术生成

概念

树形图:相当于有向树,一种没有环且除根以外的点入度为 1 1 1的有向图。
最小树形图:和最小生成树相像。是在一个有向图中能遍历到所有点且边权和最小的树形图。
朱刘算法:一种求确定根的最小树形图的算法。

方法

  1. 给当前图 G G G中每一个点找一条最小的前驱边(除根外),将前驱边构成一个新图 G ′ G' G
  2. 如果 G ′ G' G中没有环就加上所有边权并输出答案,否则就加上所有环内的边权并执行步骤 3 3 3
  3. G ′ G' G中的环对应到 G G G上,所有指向 G ′ G' G中的环的边 i , j i,j i,j的权值都减去 j j j的最小前驱边的权值。然后将 G ′ G' G中的环对应到 G G G上进行缩点,将得到的新图代替 G G G,回到步骤 1 1 1

正确性比较显然。如果 G ′ G' G中没有环,此时除根外每个点都只找了一个前驱边,显然除根外每一个点度数为 1 1 1。而且我们每一个点都找的是最小的前驱边,所以一定是最小的。如果 G G G中有环,那就换掉环中一条边,那就找换掉后增加的边权最小的,此时 3 3 3步骤中更改边权的操作已经把边权换成了该成某条边后增加的权值,而步骤 1 1 1又刚好在找增加的最小的边。因为环缩成了一个点,所以执行下来只会替换掉一条边,显然是最优的策略。

例题

AcWing 2417

模板题。需要注意的是无解当且仅当有点无法从根结点到达。

#include<bits/stdc++.h>
using namespace std;
const int NN=104;
int low[NN],dfn[NN],scc[NN],cnt,scn,pre[NN],n,m;
double x[NN],y[NN],d[NN][NN],td[NN][NN],in[NN];
bool g[NN][NN],st[NN];
stack<int>stk;
double dist(int a,int b)
{
    return sqrt((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]));
}
void dfs(int u)
{
    st[u]=true;
    for(int i=1;i<=n;i++)
        if(g[u][i]&&!st[i])
            dfs(i);
}
bool check()
{
    memset(st,false,sizeof(st));
    dfs(1);
    for(int i=1;i<=n;i++)
        if(!st[i])
            return false;
    return true;
}
void tarjan(int u)
{
    low[u]=dfn[u]=++cnt;
    in[u]=true;
    stk.push(u);
    int v=pre[u];
    if(!dfn[v])
    {
        tarjan(v);
        low[u]=min(low[u],low[v]);
    }
    else if(in[v])
        low[u]=min(low[u],dfn[v]);
    if(low[u]==dfn[u])
    {
        int sum;
        scn++;
        do
        {
            sum=stk.top();
            stk.pop();
            scc[sum]=scn;
            in[sum]=false;
        }while(sum!=u);
    }
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(int i=1;i<=n;i++)
            scanf("%lf%lf",&x[i],&y[i]);
        memset(g,false,sizeof(g));
        for(int i=1;i<=m;i++)
        {
            int a,b;
            scanf("%d%d",&a,&b);
            if(a!=b&&b!=1)
                g[a][b]=true;
        }
        if(!check())
        {
            puts("poor snoopy");
            continue;
        }
        double res=0;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                if(g[i][j])
                    d[i][j]=dist(i,j);
                else
                    d[i][j]=1e9;
        while(1)
        {
            for(int i=1;i<=n;i++)
            {
                pre[i]=i;
                for(int j=1;j<=n;j++)
                    if(d[pre[i]][i]>d[j][i])
                        pre[i]=j;
            }
            memset(dfn,0,sizeof(dfn));
            scn=cnt=0;
            for(int i=1;i<=n;i++)
                if(!dfn[i])
                    tarjan(i);
            if(scn==n)
            {
                for(int i=2;i<=n;i++)
                    res+=d[pre[i]][i];
                break;
            }
            for(int i=2;i<=n;i++)
                if(scc[pre[i]]==scc[i])
                    res+=d[pre[i]][i];
            for(int i=1;i<=scn;i++)
                for(int j=1;j<=scn;j++)
                    td[i][j]=1e9;
            for(int i=1;i<=n;i++)
                for(int j=1;j<=n;j++)
                    if(d[i][j]!=1e9&&scc[i]!=scc[j])
                    {
                        int a=scc[i],b=scc[j];
                        if(scc[pre[j]]==b)
                            td[a][b]=min(td[a][b],d[i][j]-d[pre[j]][j]);
                        else
                            td[a][b]=min(td[a][b],d[i][j]);
                    }
            n=scn;
            memcpy(d,td,sizeof(d));
        }
        printf("%.2lf\n",res);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值