Command Network - POJ 3164 朱刘算法

Command Network
Time Limit: 1000MS Memory Limit: 131072K
Total Submissions: 14424 Accepted: 4151

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

题意:问是否存在以0为根的最小树形图。

思路:

最 小树形图,就是给有向带权图中指定一个特殊的点root,求一棵以root为根的有向生成树T,并且T中所有边的总权值最小。最小树形图的第一个算法是 1965年朱永津和刘振宏提出的复杂度为O(VE)的算法。
判断是否存在树形图的方法很简单,只需要以v为根作一次图的遍历就可以了,所以下面的 算法中不再考虑树形图不存在的情况。
在所有操作开始之前,我们需要把图中所有的自环全都清除。很明显,自环是不可能在任何一个树形图上的。只有进 行了这步操作,总算法复杂度才真正能保证是O(VE)。
首先为除根之外的每个点选定一条入边,这条入边一定要是所有入边中最小的。现在所有的最小 入边都选择出来了,如果这个入边集不存在有向环的话,我们可以证明这个集合就是该图的最小树形图。这个证明并不是很难。如果存在有向环的话,我们就要将这 个有向环所称一个人工顶点,同时改变图中边的权。假设某点u在该环上,并设这个环中指向u的边权是in[u],那么对于每条从u出发的边(u, i, w),在新图中连接(new, i, w)的边,其中new为新加的人工顶点; 对于每条进入u的边(i, u, w),在新图中建立边(i, new, w-in[u])的边。为什么入边的权要减去in[u],这个后面会解释,在这里先给出算法的步骤。然后可以证明,新图中最小树形图的权加上旧图中被收缩 的那个环的权和,就是原图中最小树形图的权。
上面结论也不做证明了。现在依据上面的结论,说明一下为什么出边的权不变,入边的权要减去in [u]。对于新图中的最小树形图T,设指向人工节点的边为e。将人工节点展开以后,e指向了一个环。假设原先e是指向u的,这个时候我们将环上指向u的边 in[u]删除,这样就得到了原图中的一个树形图。我们会发现,如果新图中e的权w'(e)是原图中e的权w(e)减去in[u]权的话,那么在我们删除 掉in[u],并且将e恢复为原图状态的时候,这个树形图的权仍然是新图树形图的权加环的权,而这个权值正是最小树形图的权值。所以在展开节点之后,我们 得到的仍然是最小树形图。逐步展开所有的人工节点,就会得到初始图的最小树形图了。
如果实现得很聪明的话,可以达到找最小入边O(E),找环 O(V),收缩O(E),其中在找环O(V)这里需要一点技巧。这样每次收缩的复杂度是O(E),然后最多会收缩几次呢?由于我们一开始已经拿掉了所有的 自环,我门可以知道每个环至少包含2个点,收缩成1个点之后,总点数减少了至少1。当整个图收缩到只有1个点的时候,最小树形图就不不用求了。所以我们最 多只会进行V-1次的收缩,所以总得复杂度自然是O(VE)了。由此可见,如果一开始不除去自环的话,理论复杂度会和自环的数目有关。

下 面是朱刘算法的构造图

AC代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
struct Point
{
    double x,y;
}p[110];
struct node
{
    int u,v;
    double w;
}edge[10010];
int n,m,pre[110],id[110],vis[110],INF=1e9;
double in[110];
double dis(Point a,Point b)
{
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
double Directed_MST(int root,int V,int E)
{
    double ret=0;
    int i,j,k,u,v,cnt;
    while(true)
    {
        //1.找最小入边
        for(i=0;i<V;i++)
           in[i]=INF;
        for(i=0;i<E;i++)
        {
            u=edge[i].u;
            v=edge[i].v;
            if(edge[i].w<in[v] && u!=v)
            {
                pre[v]=u;
                in[v]=edge[i].w;
            }
        }
        for(i=0;i<V;i++)
           if(i!=root && in[i]==INF)
             return -1;
        //2.找环
        cnt=0;
        memset(id,-1,sizeof(id));
        memset(vis,-1,sizeof(vis));
        in[root]=0;
        for(i=0;i<V;i++)
        {
            ret+=in[i];
            v=i;
            while(vis[v]!=i && id[v]==-1 && v!=root)
            {
                vis[v]=i;
                v=pre[v];
            }
            if(v!=root && id[v]==-1)
            {
                for(u=pre[v];u!=v;u=pre[u])
                   id[u]=cnt;
                id[v]=cnt++;
            }
        }
        if(cnt==0)
          break;
        for(i=0;i<V;i++)
           if(id[i]==-1)
             id[i]=cnt++;
        //3.建立新图
        for(i=0;i<E;i++)
        {
            u=edge[i].u;
            v=edge[i].v;
            edge[i].u=id[u];
            edge[i].v=id[v];
            if(id[u]!=id[v])
              edge[i].w-=in[v];
        }
        V=cnt;
        root=id[root];
    }
    return ret;
}
int main()
{
    int i,j,k;
    double ans;
    while(~scanf("%d%d",&n,&m))
    {
        for(i=0;i<n;i++)
           scanf("%lf%lf",&p[i].x,&p[i].y);
        for(i=0;i<m;i++)
        {
            scanf("%d%d",&edge[i].u,&edge[i].v);
            edge[i].u--;edge[i].v--;
            if(edge[i].u!=edge[i].v)
              edge[i].w=dis(p[edge[i].u],p[edge[i].v]);
            else
              edge[i].w=INF;
        }
        ans=Directed_MST(0,n,m);
        if(ans==-1)
          printf("poor snoopy\n");
        else
          printf("%.2f\n",ans);
    }
}



评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值