POJ 3164 Command Network 最小树形图,朱刘算法 难度:2

Command Network
Time Limit:1000MS     Memory Limit:131072KB     64bit IO Format:%I64d & %I64u

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

读题:注意unidirectional 和 enable littenken’s commands to reach every disconnected node 
思路:朱刘算法

开始遍历所有节点看看有没有根不能到的点如果有的话就构不成最小树形图
首先说说我理解的朱刘算法
其实就是有向图版的kruscal
首先求出所有除了根以外的节点最短的入边,因为根不需要有点到达它所以不用连,连了就不是有向最小生成树了
如果这个时候没有环了,也就是所有的点都用最短的边与根树相连了,肯定为所求
如果有环的话,那么肯定有某条边是不需要的,因为有环的时候整棵树肯定还是不连通的,(只有n-1条边)所以某条边要被选出来和其他的子树相连,和一个其他的子树相连只用选一条,因为再选其他的边和那棵子树连只是多使一条边不是最优,那么怎么选择这条边呢?肯定选择使改变后增量最小的边,也就是去掉"边权本身在环里最大"的边,连上"现在不在这个连通块里但加上这条边就可以到达这个联通块边权最小的边"所以智慧的朱刘算法会在每次给从每个环出发的边都减去这个环现在更新的那条边(这条边肯定是最大的),得到增量,为了简化运算,所以朱刘算法把一个环缩成一个点下次循环的时候拆开,我试着用kruscal不缩点感觉很麻烦就没在写下去了,当然,如果不用更新增量就会是0
朱刘算法的过程:
找所有除了根以外的最小入边
看看有没有孤立点,有的话就构不成最小树形图
对点连边,对上次的环改边,对这次的寻环
没有环的话整个图就稳定了,输出答案
把新环缩为一点
更新各边的增量

#include <cstdio>
#include <cstring>
#include <vector>
#include <cmath>
#include <algorithm>
#include <assert.h>
using namespace std;
const int maxn=110;
const int maxm=10010;
int n,m;

int x[maxn],y[maxn];//存储节点坐标
struct edge{
    int from,to;
    double cost;
};//dis to;
edge e[maxm];//存储具体边,邻接表不太好改起终点,那样需要中间的数组记录

int ID[maxn];//缩点之后,现在整条分量代表的点
int num[maxn];//记录遍历起点,再次遇到num[v]=i就形成环了,这个环不一定是从起点到终点的
double mincost[maxn];//最小入边长度,反正一会儿需要减掉不需要记录是从哪里来的,只要合乎条件
int pre[maxn];//前驱 节点,记录最小入边来自哪个点,用来在环上遍历


bool vis[maxn];//dfs用
vector<int >G[maxn];
void dfs(int s){//有没有无所谓
    vis[s]=true;
    for(int i=0;i<G[s].size();i++){
        int t=G[s][i];
        if(!vis[t]){
                dfs(t);
        }
    }
}

const double INFNITE=~0u>>1;//学到新姿势
double zhuliu(){
    int cnt1=n,cnt2=1;//因为要缩点,cnt1是上一轮得到的点数,cnt2是这一轮缩成的点数
    double ans=0;//记录答案

    while(1){
        fill(mincost,mincost+n+1,INFNITE);//每次都重新搜索,已经连接的边要么增量为0要么已经在同一个缩点里了
        for(int i=0;i<m;i++){//寻找最小入边
            int f=e[i].from;
            int t=e[i].to;
            double c=e[i].cost;
            
            if(mincost[t]>c&&f!=t){
                mincost[t]=c;//更新最小入边
                pre[t]=f;//记录前驱节点
            }
        }

        for(int i=2;i<=cnt1;i++){//判孤立点
           assert(mincost[i]!=INFNITE);//直接用这个判也行,现在用过dfs就不可能有点连不上了
        }

        memset(ID,0,sizeof(ID));
        memset(num,0,sizeof(num));
        cnt2=1;
        mincost[1]=0;
        ID[1]=1;
        for(int i=1;i<=cnt1;i++){//寻环标号,这里标的序号方便最后缩点,所以我从2开始标环,1是根不变
            ans+=mincost[i];//上一次遍历时的某个向量现在与某条边相连,答案中改入这条边先,如果构成环,下一次遍历会删掉它(从环拆成的向量或者某个点)
            int v=i;//如果不在环里增量是0
            while(ID[v]==0&&num[v]!=i&&v!=1){
                num[v]=i;
                v=pre[v];
            }
            if(ID[v]==0&&v!=1){//如果成环了
                ID[v]=++cnt2;
                for(int u=pre[v];u!=v;u=pre[u]){
                    ID[u]=cnt2;
                }
            }
        }

        if(cnt2==1)return ans;
        for(int i=1;i<=cnt1;i++)if(ID[i]==0)ID[i]=++cnt2;//不在环里的也标上号,这个点之前是被遍历过的

        for(int i=0;i<m;i++){//更新增量
            int v=e[i].to;
            e[i].from=ID[e[i].from];e[i].to=ID[e[i].to];//把边的起终点也更新了
            int f=e[i].from;
            int t=e[i].to;
            if(f!=t){
                e[i].cost-=mincost[v];
            }
        }

        cnt1=cnt2;
    }
}

int main(){
    while(scanf("%d%d",&n,&m)==2){
        for(int i=1;i<=n;i++){
            scanf("%d%d",x+i,y+i);
            G[i].clear();
        }
        int f,t;
        for(int i=0;i<m;i++){
            scanf("%d%d",&f,&t);
            e[i].cost=sqrt((double)((x[f]-x[t])*(x[f]-x[t])+(y[f]-y[t])*(y[f]-y[t])));
            e[i].from=f;
            e[i].to=t;
            G[f].push_back(t);
        }
        memset(vis,0,sizeof(vis));
        dfs(1);
        bool fl=false;
        for(int i=1;i<=n;i++){
            if(!vis[i]){
                fl=true;
                break;
            }
        }
        if(fl){
            printf("poor snoopy\n");
            continue;
        }
        double ans=zhuliu();
        printf("%.2f\n",ans);
    }
    return 0;
}

 

posted @ 2014-08-15 16:07  雪溯  阅读(221)  评论(0)    收藏  举报