POJ3164_Command Network_有向图::最小树状图::朱刘算法

题意

有向图中有 N 个点,M 条边。建立最小树状结构,使从 1 点出发能到达其余各点。

思路

有向图最小树状图(DSMT)裸题,朱刘算法。

朱刘算法

算法过程

1.对于每一个点找出它的权值最小的入边。同时判断从 根 点出发能否到达其余各点,不能则不能构成 DSMT。
2.判断图中是否存在环。如果不存在环,则将前一步中各点的最小入边权加和就是答案。如果有环,则需要将环缩成一个点,建立新图进行求解。
3.缩点。特别的,对于终点在环上的边,其权值需改变(见AC代码)。对新图进行求解。

提醒

1.朱刘算法的前提是已经屏蔽了原图中的自环。自环显然不会参与构成最小最小树形图。
2.除了边数组外,算法主要用到四个辅助数组 Pre, Id, Vis, In。
3.算法函数中的 res 只需要在开头初始化一次。每次建立新图后重新求解的答案直接累加到 res 上。
4.要注意判环之前的四个初始化操作。
5.注意缩点时点权值改变的公式。这里把不在环上的边全都改变了,因为不再环上的边的最小权已经加到 res 中了。不改环上的边是因为它们在新图中被变成了自环,不再参与运算了。

题目链接

http://poj.org/problem?id=3164

AC代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>

using namespace std;

const int maxn = 100 + 10;
const int maxm = 1e4 + 10;
const double inf = 1e9;

int X[maxn], Y[maxn];

double dist(int a, int b)
{
    if(a == b) return inf;
    return sqrt(1.0 * ((X[a] - X[b]) * (X[a] - X[b]) + (Y[a] - Y[b]) * (Y[a] - Y[b])));
}

/************************朱刘算法模板****************************/
int N, M;

int From[maxm], To[maxm];                                   //边的起点、终点、权值
double Cost[maxm];

int Pre[maxn], Id[maxn], Vis[maxn];                         //DSMT中点的前驱,锁点后的新编号,找环时的标记数组
double In[maxn];                                            //每个点的最小入边权

double DSMT(int root)                                       //root是根,出发点
{
    double res = 0.0;                                       //定义在最开头整个算法只需要初始化这一次

    while(1)
    {
        for(int i= 1; i<= N; i++) In[i] = inf;              //求最小入边权,同时求出前驱
        for(int i= 1; i<= M; i++)                           //这里要注意排除自环
        {
            int u = From[i], v = To[i];
            if(In[v] > Cost[i] && u != v)
            {
                In[v] = Cost[i];
                Pre[v] = u;
            }
        }

        for(int i= 1; i<= N; i++)                           //从root出发,有的点不能到达,不能构造DSMT
            if(i != root && In[i] == inf) return -1;

        In[root] = 0;                                       //根的最小入边权置为0
        int node = 0;                                       //缩点后,新的点数
        memset(Id, -1, sizeof Id);                          //初始化缩点后,每个点新的标号
        memset(Vis, -1, sizeof Vis);                        //初始化访问标记数组

        for(int i= 1; i<= N; i++)
        {
            res += In[i];                                   //累加各点的最小入边权

            int v = i;                                      //沿着前驱找,判断是否有环
            while(Vis[v] != i && Id[v] == -1 && v != root)  //Vis[v] == i表示这个点在这次查找中已经出现过一次,找到环
            {                                               //Id[v] != -1表示这个点已经在环上了
                Vis[v] = i;
                v = Pre[v];
            }

            if(Id[v] == -1 && v != root)                    //满足这两个条件说明是找到环的情况
            {                                               //给环上点一个统一的新标号
                ++ node;

                for(int u = Pre[v]; u != v; u = Pre[u])     //仍然是沿着前驱改下去
                    Id[u] = node;
                Id[v] = node;                               //别忘了v点
            }
        }

        if(node == 0) break;                                //新点数为0,表示没有出现环,res就是答案
/*缩点,重建新图过程*/
        for(int i= 1; i<= N; i++)                           //首先给不在环上的点赋一个新标号
            if(Id[i] == -1) Id[i] = ++ node;

        for(int i= 1; i<= M; i++)                           //将边和新标号对应起来
        {
            int v = To[i];                                  //保留边中原始的终点,因为它对应着v的入边权

            From[i] = Id[From[i]];                          //更新边信息
            To[i] = Id[To[i]];

            if(From[i] != To[i]) Cost[i] -= In[v];          //环以外的边需要进行处理
        }

        N = node;                                           //更新点的数目
        root = Id[root];                                    //更新根的标号
    }

    return res;
}
/************************朱刘算法模板****************************/

int main()
{
    while(scanf("%d %d", &N, &M) != EOF)
    {
        for(int i= 1; i<= N; i++)
            scanf("%d %d", X + i, Y + i);

        for(int i= 1; i<= M; i++)
        {
            scanf("%d %d", &From[i], &To[i]);
            Cost[i] = dist(From[i], To[i]);
        }

        double ans = DSMT(1);

        if(ans == -1) printf("poor snoopy\n");
        else printf("%.2f\n", ans);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值