您的位置:首页 > 其它

poj 3164 Command Network(有定根的最小树形图)

2017-04-24 20:36 549 查看
                                                                              Command Network

Time Limit: 1000MSMemory Limit: 131072K
Total Submissions: 18060Accepted: 5159
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 integerN (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 nextN lines each contain an ordered pair
xi and yi, giving the Cartesian coordinates of the nodes. Then followM lines each containing two integers
i and j between 1 andN (inclusive) meaning a wire can be built between node
i and nodej 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
POJ Monthly--2006.12.31, galaxy

之前就有看过最小树形图,但是不是很明白,只会套模版,没办法渣渣都是这。。。。

现在又看了一下,感觉对它的理解又深入了不少,说一下自己的理解吧。

最小树形图,也是有向图的最小生成树。首先我们是要先找到每个点的最小入边的,把它保存在ret中,如果有一个点没有入边(除了超级节点0)的话,那么就没办法构成最小树形图。

但是这时候的ret还不是我们想要的答案,因为有可能会有环存在,那么这个时候我们就要删除一些边了。

比如1->2, 2->1, 5->1, 2->4,3->1,他们的权值分别为2, 1, 5, 4, 3,很明显这时候2和1构成环了,但是呢1的最小入边还是2->1的这条边,那么我们这个时候就要把环拆开,也就是要把这条边删除,那么1的最下入边就变成3->1的这条边了,因为原来就已经把2->1的这条边的权值就进去了,那这个时候需要减去这个权值,再加入3->1的权值,也就是我们只用在加上两个权值的差值即可。

然后我们在构成新图,再这样一直按照最小树形图规则循环下去,直到没有环位置。

还不懂得可以看下面代码中的注释。

回到这道题:
题意:
给出N个点M条边,节点的编号从1~N,然后接下来N行给出每个点的坐标。然后给出M条有向边。点与点之间的距离是边的权值。然后根节点永远是1。求将所有点

连接起来的用的最小网线长度。因为战争原因网线稀缺,不要求每两个点直接连通。只要间接连通即可。

还有这道题应该是编译器的问题,最后的结果只能用%f输出。

AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#define INF 0x3f3f3f3f
using namespace std;
struct node
{
double x, y;
}Node[104];
struct edge
{
int u, v;
double length;
}Edge[10001];
int n, m;
int id[104], vis[104], pre[104];
double in[104];
double cal(node a, node b)  ///计算两点距离
{
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
double ZL(int root, int N, int M)
{
double ret = 0;
while(true)
{
///1.找最小入边
for(int i = 0; i < N; i++) ///刚开始把所有的点的入边都刷最大
in[i] = INF;
for(int i = 0; i < M; i++) ///找到每一个点的最小入边
{
int u = Edge[i].u;
int v = Edge[i].v;
if(Edge[i].length < in[v] && u != v) ///如果当前点u到v的边比v之前的入边都小 那么更新
{
pre[v] = u;    ///pre数组存的是指向v的最小入边的点是u
in[v] = Edge[i].length; ///如果有更小的边 则更新
}
}
for(int i = 0; i < N; i++)
{
if(i == root) continue;
if(in[i] == INF)   ///除了跟以外有点没有入边,则根无法到达它 就没办法构成最小树形图
return -1;
}
///找环
int cnt = 0; ///新图的编号
memset(id, -1, sizeof(id));  ///存新图中各点的编号
memset(vis, -1, sizeof(vis));
in[root] = 0; ///root节点是没有入边的 所以刷成0
for(int i = 0; i < N; i++) ///标记每个环
{
ret+=in[i];
int v = i;
while(vis[v] != i&&id[v] == -1 &&v != root)///每个点寻找其前序点,要么最终寻找至根部,要么找到一个环
{
vis[v] = i;
v = pre[v];///找到指向v这个点的最小入边的点
}
if(v != root&&id[v] == -1) ///缩点  把同处于一个环中的各个点都标为人工节点cnt
{
for(int u = pre[v]; u != v; u = pre[u])
id[u] = cnt;
id[v] = cnt++;
}
}
if(cnt == 0) break; ///无环 则break
for(int i = 0; i < N; i++)///为缩点后构成的新图进行编号
if(id[i] == -1)
id[i] = cnt++;
///建新图
for(int i = 0; i < M; i++)
{
int u = Edge[i].u;///先保存下来原来节点的编号
int v = Edge[i].v;
Edge[i].u = id[u]; ///更新为新图中的编号
Edge[i].v = id[v];
if(id[u] != id[v])
///u和v不在一个环中 假设一个点的最小入边是环中的边  那么等我们把环缩点
///在删边的时候 那么我们肯定要把环中多加的边删去  在加上u到v的边
///但是这样的话 我们还要在判断边的情况  但是我们要是直接用我要加上两条边的相对值 不就方便很多了吗  也不用再判断边的情况了
Edge[i].length -= in[v];
}
N = cnt; ///更新节点个数
root = id[root];///更新根节点
}
return ret;
}
int main()
{
while(~scanf("%d%d",&n,&m)) ///0 ~ n - 1 n个节点  m条边
{
for(int i = 0; i < n; i++)
scanf("%lf%lf",&Node[i].x, &Node[i].y);
for(int i = 0; i < m; i++)
{
scanf("%d%d",&Edge[i].u, &Edge[i].v);
Edge[i].u--;
Edge[i].v--;
///朱刘算法之所以可以达到O(VE),就是因为把自环去掉  这样在进行找环 缩点的时候可以节约大量的时间
if(Edge[i].u != Edge[i].v)
Edge[i].length = cal(Node[Edge[i].u], Node[Edge[i].v]);
else Edge[i].length = INF; ///去除自环
}
double ans;
ans = ZL(0, n, m);
if(ans == -1)
printf("poor snoopy\n");
else
printf("%.2f\n", ans);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息