Gragh---Algorithm ---最小树形图

定义:

定根的最小树形图,就是给有向带权图中指定一个特殊的点root,求一棵以root为根的有向生成树T,并且T中所有边的总权值最小。


算法实现过程:(定根)

朱-刘算法的大概过程如下:参考:hqd_acm的专栏 +幻影阁

首先消除自环,显然自环不在最小树形图中。然后判定是否存在最小树形图,以根为起点DFS一遍即可。判断图的连通性,若不连通直接无解,否则一定有解。

之后进行以下步骤。

设cost为最小树形图总权值。
0.置cost=0。
1.求最短弧集合Ao (一条弧就是一条有向边)

除源点外,为所有其他节点Vi,找到一条以Vi为终点的边,把它加入到集合Ao中。

(加边的方法:所有点到Vi的边中权值最小的边即为该加入的边,记prev[vi]为该边的起点,mincost[vi]为该边的权值)

2.检查Ao中的边是否会形成有向圈,有则到步骤3,无则到步骤4。(可利用并查集)

(判断方法:利用prev数组,枚举为检查过的点作为搜索的起点,做类似DFS的操作)

3.将有向环缩成一个点。
假设环中的点有(Vk1,Vk2,… ,Vki)总共i个,用缩成的点叫Vk替代,则在压缩后的图中,其他所有不在环中点v到Vk的距离定义如下:
<1> gh[v][Vk]=min { gh[v][Vkj]-mincost[Vkj] } (1<=j<=i)而Vk到v的距离为
<2> gh[Vk][v]=min { gh[Vkj][v] }              (1<=j<=i)
同时注意更新prev[v]的值,即if(prev[v]==Vkj) prev[v]=Vk
另外cost=cost+mincost[Vkj] (1<=j<=i)

到步骤1.

<1>的理解:先假设环上所有边均选上,若下次选择某一条边进入该环,则可以断开进入点与进入点的前驱之间的边,即断开F[进入点],因为之前已经把现有的最小值加到集合A0里面了,所以现在只要加上多出来的那一部分,也就是min{a[p,Vi]-f[Vi]},所以等效为直接把a[p,node]赋值为min{a[p,Vi]-f[Vi]},有点像增广的感觉,每次找到delta。

附一张图片加以理解:





4.cost加上Ao的权值和即为最小树形图总权值。



复杂度:有固定根

找环O(V),收缩O(E),总复杂度O(VE)。


模板:(POJ 3164 裸的最小树形图)

#include <cstdio>
#include <iostream>
#include <algorithm>
#include  <cmath>
#include <vector>
#include <cstring>
using namespace std;
const int inf=0x3f3f3f3f;
int n,m;
struct point
{
    int x,y;
}P[105];

struct Edge
{
    int u,v;
    double w;
}edge[10005];

int pre[105],color[105],mark[105];
int cnt_edge;
double in[105];

void AddEdge(int u,int v,double w)
{
    edge[cnt_edge].u=u;
    edge[cnt_edge].v=v;
    edge[cnt_edge].w=w;
    cnt_edge++;
}

double Cal(int x1, int y1,int x2, int y2)
{
    return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}

double zhuliu(int root,int n,int m)
{
    int i,j,u,v,cnt;
    double w;
    cnt=0;
    double res=0;

    while(1)
    {
        for(i=1;i<=n;i++)
            in[i]=inf;
        for(i=0;i<m;i++)
        {
            u=edge[i].u;
            v=edge[i].v;
            w=edge[i].w;
            if(in[v]>w&&u!=v)
            {
                in[v]=w;
                pre[v]=u;
            }
        }
        for(i=1;i<=n;i++)
        {
            if(i!=root&&in[i]==inf)
            return -1;
        }
        memset(mark,0,sizeof(mark));
        memset(color,0,sizeof(color));
        mark[root]=1;
        in[root]=0;
        cnt=0;

        for(i=1;i<=n;i++)
        {
            res+=in[i];
            v=i;
            while(mark[v]!=i&&color[v]==0&&v!=root)
            {
                mark[v]=i;
                v=pre[v];
            }
            if(v!=root&&color[v]==0)
            {
                cnt++;
                for(u=pre[v];u!=v;u=pre[u])
                    color[u]=cnt;
                color[v]=cnt;
            }
        }

        if(cnt==0)
            break;
        for(i=1;i<=n;i++)
            if(color[i]==0)
                color[i]=++cnt;
        for(i=0;i<m;i++)
        {
            v=edge[i].v;
            edge[i].u=color[edge[i].u];
            edge[i].v=color[edge[i].v];
            if(edge[i].u!=edge[i].v)
                edge[i].w-=in[v];
        }

        n=cnt;
        root=color[root];
    }
    return res;
}

int main()
{
    //freopen("in","r",stdin);
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        int i,j,f,t;
        cnt_edge=0;
        for(i=1;i<=n;i++)
            scanf("%d %d",&P[i].x,&P[i].y);
        for(i=0;i<m;i++)
        {
            scanf("%d%d",&f,&t);
            if(f!=t)
            AddEdge(f,t,Cal(P[f].x,P[f].y,P[t].x,P[t].y));
        }
        double ans=zhuliu(1,n,cnt_edge);
        if(ans!=-1)
        printf("%.2f\n",ans);
        else printf("poor snoopy\n");
    }
    return 0;
}





  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值