最小树形图--有向图的最小生成树 poj 3164 Command Network

【最小树形图】:

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

最小树形图必须有一个根,而且选择不同的点作为根,也是不一样的结果。

最小树形图必须包含图中的每一个节点,并且均可通过有向边到达根节点root

最小树形图的第一个算法是 1965年朱永津和刘振宏提出的复杂度为O(VE)的算法。

【解题思路】:

朱刘算法的思路。

首先为除root之外的每一个点,找一个最小的前驱边。遍历后会找到n-1条这样的边,如果不构成有向环,则这n-1条边构成的树就是有向图的最小生成树。


若存在有向环路,则要取环,也是难点。

朱刘算法是通过缩点法来去环的。


缩点法:将一个有向环,其上的所有点的信息,转移到其中一个点,从而将这个环化为一个点。

缩点之后,再找环路,重复缩点法,直到不存在环路,就得到了最小树形图。


当然在缩点的时候,需要将环上点的边信息都转移到缩点上。

这里的转移方法是:先将环路的权值累加保存,然后让每一个环外边都减掉这个环外边指向的环内边。

这个地方需要好好理解一下,如图环路2345,上面那句话的意思就是边<6,5>指向5,他要减去<4,5>


为什么呢?

对这个环来说,势必要破掉一个边,假如是<4,5>,那么就需要一个其他的边来指向5,这个图上也就是<6,5>

由于环路的权值已经保存,我们让<6,5>减掉一个<4,5>,再把<6,5>的值加进答案,就不受<4,5>影响了

也就是有环路的时候我们加了一个<4,5>,破环之后又减掉了一个<4,5>


当重复这种操作,直到没有环路时,算法结束,为每个点配的前驱边,之和,就是最小树形图的权值


这种算法方便计算出最小树形图的权值,

若要得到这棵树,需要多加考虑,这里没涉及。

【例题poj3164】

Command Network
Time Limit: 1000MS
Memory Limit: 131072K
Total Submissions: 18943
Accepted: 5452

Description

After a long lasting war on words, a war on arms finally breaks out between littleken’s and KnuthOcean’s kingdoms. A sudden and violent assault by KnuthOcean’s force has rendered a total failure of littleken’s command network. A provisional network must be built immediately. littleken orders snoopy to take charge of the project.

With the situation studied to every detail, snoopy believes that the most urgent point is to enable littenken’s commands to reach every disconnected node in the destroyed network and decides on a plan to build a unidirectional communication network. The nodes are distributed on a plane. If littleken’s commands are to be able to be delivered directly from a node A to another node B, a wire will have to be built along the straight line segment connecting the two nodes. Since it’s in wartime, not between all pairs of nodes can wires be built. snoopy wants the plan to require the shortest total length of wires so that the construction can be done very soon.

Input

The input contains several test cases. Each test case starts with a line containing two integer N (N ≤ 100), the number of nodes in the destroyed network, and M (M ≤ 104), the number of pairs of nodes between which a wire can be built. The next N lines each contain an ordered pair xi and yi, giving the Cartesian coordinates of the nodes. Then follow M lines each containing two integers i and j between 1 and N (inclusive) meaning a wire can be built between node i and node j for unidirectional command delivery from the former to the latter. littleken’s headquarter is always located at node 1. Process to end of file.

Output

For each test case, output exactly one line containing the shortest total length of wires to two digits past the decimal point. In the cases that such a network does not exist, just output ‘poor snoopy’.

Sample Input

4 6
0 6
4 6
0 0
7 20
1 2
1 3
2 3
3 4
3 1
3 2
4 3
0 0
1 0
0 1
1 2
1 3
4 1
2 3

Sample Output

31.19
poor snoopy
【分析】:

此题是double型,特别注意poj的判题,scanf用%lf,printf用%f

题意是输入n个点的坐标,和m条有向边,问是否存在以1为根的生成树

若有,输出最小树形图的权值

【代码】:

#include <stdio.h>
#include <math.h>
#include <string.h>
const int INF=0x3f3f3f3f;
double p[105][2];//坐标
int pre[105];//最短弧前驱
int vis[105];//点标记
int used[105];//查环过程的标记
double Map[105][105];//存图关系
int n,m,u,v,len;
double length(double x1,double y1,double x2,double y2){
    return sqrt( (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2) );
}
double zhuliu(int root)
{
    double sum=0;
    int i,j,k;
    memset(vis,0,sizeof(vis));
    while(1)
    {
        for(i=1;i<=n;i++)//1、求最短弧集合pre
        {
            if(vis[i]||i==root)continue;
            pre[i]=i;
            for(int j=1;j<=n;j++)//找i点的最短前驱弧
                if(!vis[j]&&Map[j][i]<Map[pre[i]][i])
                    pre[i]=j;
            if(pre[i]==i)return -1;//弱图不连通
        }
        for(i=1;i<=n;i++)//2、查环
        {
            if(vis[i]||i==root)continue;
            memset(used,0,sizeof used);
            used[root]=1;
            k=i;
            while(!used[k]){
                used[k]=1;
                k=pre[k];
            }
            if(k!=root)break;//存在环
        }
        if(i>n)//不存在环了
        {
            for(j=1;j<=n;j++)
                if(j!=root&&!vis[j])
                    sum+=Map[pre[j]][j];
            return sum;
        }
        i=k;  //3、下面将这个环缩到i点;
        do{   //4、先累加环记录下环权值
            sum+=Map[pre[k]][k];
            k=pre[k];
        }while(k!=i);
        do{//5、修改环上点的前驱边,为准备环收缩
            for(j=1;j<=n;j++)
                if(!vis[j]&&Map[j][k]<INF&&j!=pre[k])
                    Map[j][k]-=Map[pre[k]][k];
            k=pre[k];
        }while(k!=i);
        for(j=1;j<=n;j++)//6、环收缩到i点
        {
            if(j==i||vis[j])continue;
            for(k=pre[i];k!=i;k=pre[k])//k点的对外距离给i点
            {
                if(Map[i][j]>Map[k][j])Map[i][j]=Map[k][j];
                if(Map[j][i]>Map[j][k])Map[j][i]=Map[j][k];
            }
        }
        for(k=pre[i];k!=i;k=pre[k])vis[k]=1;//7、将环上除i外全标记
    }
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=0;i<104;i++)
            for(int j=0;j<104;j++)
                Map[i][j]=INF*1.0;
        for(int i=1;i<=n;i++)
            scanf("%lf%lf",&p[i][0],&p[i][1]);
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&u,&v);
            if(u!=v)
            Map[u][v]=length(p[u][0],p[u][1],p[v][0],p[v][1]);
        }
        double ans=zhuliu(1);
        if(ans<0)puts("poor snoopy");
        else
            printf("%.2f\n",ans);
    }
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雪的期许

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值