poj 3164 Command Network(最小树形图模板题)

链接:

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


题目:

Command Network
Time Limit: 1000MS Memory Limit: 131072K
Total Submissions: 8922 Accepted: 2609

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

Source




分析与总结:

1. 没注意读题,把unidirectional看成了undirectional, 一个字母之差,意思却天壤之别,Orz...(PS:  unidirectional 单向的)

2. 知道了是单向之后,还是很天真的认为就是最小生成树,用了prim算法,结果WA了

3. 本题是最小树形图,解决这个问题的算法是朱永津与刘振宏在上世纪60年代解决的(朱刘算法),终于遇到个中国人命名的算法了 = =



在网上找了几分资料,但感觉对于自环收缩这个最小树形图最核心的部分都讲得不够清楚,看得我真是一头雾水啊,

百度百科那个过程图,对于初学者来说又过于复杂了。

最终还是看了这个大神的博客才真正的懂了,讲解得通俗易懂,过程图也很赞,看了就懂,大爱啊羡慕

http://hi.baidu.com/lydrainbowcat/item/5fbae3fb9c159c5ec8f33753

以下转自该博客:

题目大意:给定一个有向图,根节点已知,求该有向图的最小树形图。最小树形图即有向图的最小生成树,定义为:选择一些边,使得根节点能够到达图中所有的节点,并使得选出的边的边权和最小。

题目算法:朱-刘算法(即由中国人朱永津和刘振宏共同发明的算法)。

算法步骤如下:(本文不再证明,参考下面给出的我自己画的一个图即可理解)

1.判断图的连通性,若不连通直接无解,否则一定有解。

2.为除了根节点以外的所有点选择一个权值最小的入边,假设用pre数组记录前驱,f数组记录选择的边长,记所选边权和为temp。

3.(可利用并查集)判断选择的的边是否构成环,若没有则直接ans+=temp并输出ans,若有,则进行下一步操作。

4.对该环实施缩点操作,设该环上有点V1,V2……Vi……Vn,缩成的点为node ,对于所有不在环中的点P进行如下更改:

(1) 点P到node的距离为min{a[p,Vi]-f[Vi]} (a为边集数组)

 (2)点node到p的距离为min{a[Vi,p]}

操作(1)的理解:先假设环上所有边均选上,若下次选择某一条边进入该环,则可以断开进入点与进入点的前驱之间的边,即断开F[进入点],所以等效为直接把a[p,node]赋值为min{a[p,Vi]-f[Vi]}。

特别提醒:本题有自环,可以提前删掉,因为它没有用。




除了这道模板题,有还有模板题:

UVa  11183 - Teen Girl Squad

TJU   2248  Channel Design



用邻接矩阵实现的代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;

const int VN = 105;
const int INF = 0x7fffffff;

template<typename Type>
class Directed_MST{
public:
    void init(int _n){
        n=_n; 
        ans = 0;
        memset(vis, 0, sizeof(vis));
        memset(inc, 0, sizeof(inc));
        for(int i=0; i<=n; ++i){
            w[i][i] = INF;
            for(int j=i+1; j<=n; ++j)
                w[i][j]=w[j][i]=INF;
        }
    }
    void insert(int u, int v, Type _w){
        if(w[u][v]>_w) w[u][v] = _w;
    }
    Type directed_mst(int u){
        //==  步骤1: 判断能否形成最小树形图,直接dfs遍历 
        dfs(u);
        for(int i=1; i<=n; ++i)
            if(!vis[i]) { return -1; }

        //== 如果可以形成最小树形图,继续
        memset(vis, 0, sizeof(vis));
        while(true){
            //== 1. 找最小前驱边  
            for(int i=1; i<=n; ++i)if(i!=u&&!inc[i]){
                w[i][i]=INF, pre[i] = i;
                for(int j=1; j<=n; ++j)if(!inc[j] && w[j][i]<w[pre[i]][i]){
                    pre[i] = j;
                }
            }
            //== 2.判断是否有环
            int i;
            for(i=1; i<=n; ++i)if(i!=u&&!inc[i]){
                int j=i, cnt=0;
                while(j!=u && pre[j]!=i && cnt<=n) j=pre[j], ++cnt;
                if(j==u || cnt>n) continue; //没找到
                break;
            }

            //== 没有找到环,得到答案              
            if(i>n){  
                for(int i=1; i<=n; ++i)if(i!=u && !inc[i]) ans+=w[pre[i]][i];
                return ans;
            }
            //==  有环,进行收缩  
            int j=i;
            memset(vis, 0, sizeof(vis));
            do{
                ans += w[pre[j]][j], j=pre[j], vis[j]=inc[j]=true;
            }while(j!=i);
            inc[i] = false; // 环缩成了点i,点i仍然存在

            //==  收缩
            for(int k=1; k<=n; ++k)if(vis[k]){ // 在环中点点
                for(int j=1; j<=n; ++j)if(!vis[j]){  // 不在环中的点
                    if(w[i][j] > w[k][j]) w[i][j] = w[k][j];
                    if(w[j][k]<INF && w[j][k]-w[pre[k]][k] < w[j][i])
                        w[j][i] = w[j][k] - w[pre[k]][k];
                }  
            }
        }
        return ans;      
    }

private:
    // 从根结点遍历一遍,判断是否存在最小树形图
    void dfs(int u){
        vis[u] = true;
        for(int i=1; i<=n; ++i)if(!vis[i]&&w[u][i]<INF){
            dfs(i);
        }
    }

private:
    Type ans;         // 所求答案
    int n;            // 结点个数
    int pre[VN];      // 权值最小的前驱边
    bool vis[VN];     // 是在环中还是在环外
    bool inc[VN];     // 该点是否被删除了(收缩)
    Type w[VN][VN];   // 图
};


Directed_MST<double>G;
double x[VN],y[VN];
inline double getDist(double x1,double y1,double x2,double y2){
    return sqrt(pow(x1-x2,2)+pow(y1-y2,2));
}

int main(){
    int n,m;
    while(~scanf("%d%d",&n,&m)){
        G.init(n);
        for(int i=1; i<=n; ++i)
            scanf("%lf%lf",&x[i],&y[i]);
        for(int i=0; i<m; ++i){
            int a,b;
            scanf("%d%d",&a,&b);
            if(a==b)continue;
            G.insert(a,b,getDist(x[a],y[a],x[b],y[b]));
        }
        double ans = G.directed_mst(1);
        if(ans < 0) puts("poor snoopy");
        else printf("%.2f\n", ans);
    }
    return 0;
}



 
 

——  生命的意义,在于赋予它意义。

          
     原创 http://blog.csdn.net/shuangde800 , By   D_Double  (转载请标明)





评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值