最小树形图--有向图的最小生成树 poj 3164 Command Network
  Lddcitr3akMp 2023年11月02日 43 0


【最小树形图】:

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

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

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

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

【解题思路】:

朱刘算法的思路。

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


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

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


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

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


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

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

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

最小树形图--有向图的最小生成树  poj 3164 Command Network_权值

为什么呢?

对这个环来说,势必要破掉一个边,假如是<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;
}


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  FoZN5OJ14wRT   2023年11月28日   32   0   0 Luaideideluasedsed
  vv2O73UnQfVU   2023年11月12日   35   0   0 cisedideidecised
Lddcitr3akMp
最新推荐 更多

2024-05-31