题目描述
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.输入格式
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.
输出格式
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’.
输入样例
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输出样例
31.19
poor snoopy
分析
题目大意是给定n个点的坐标,以及m条有向边,每条边的权值为两点的间距,求能否以1号点为根节点形成一颗最小树形图,如果可以输出这颗树的权值,否则输出“poor snoopy”。
典型的最小树形图模板题,正解是通过朱刘算法,对于朱刘算法不了解的可以看看这篇博客,主要的算法流程是这样的:
- 求各点最小入边权值in[i]。
- 判断除根节点外是否所有点都有入边,有则继续3,无则无法建树形图,输出“poor snoopy”。
- 缩点并统计环,如果有环就继续4,无环则表示已找到最小树形图,输出其权值即可。
- 建新图,对于边<u,v>,如果点u和v不在一个环路呢,则边<u,v>的权值减小in[v]。
- 继续从1-4,直到图中无环。
以下是源代码。
源程序
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define MAXN 105
#define MAXM MAXN*MAXN
#define INF 999999999
using namespace std;
struct Pos{
double x,y;
}pos[MAXN];
struct Edge{
int u,v;
double w;
}edge[MAXM];
int id[MAXN],pre[MAXN];
double dis[MAXN]; //最小入边
bool vis[MAXN];
double zhuliu(int root,int n,int m)
{
double res=0;
while(1){
for(int i=1;i<=n;i++) //初始化
dis[i]=INF;
for(int i=1;i<=m;i++){ //计算各点最小入边与前驱节点
int u=edge[i].u,v=edge[i].v;
if(edge[i].w<dis[v]&&u!=v){
pre[v]=u;
dis[v]=edge[i].w;
}
}
dis[root]=0;
for(int i=1;i<=n;i++) //判断能否成树
if(int(dis[i])==INF)
return -1;
int cnt=0; //记录环数
memset(id,-1,sizeof(id));
memset(vis,false,sizeof(vis));
for(int i=1;i<=n;i++){ //标记每个环
res+=dis[i]; //记录权值
int v=i;
while(vis[v]!=i&&id[v]==-1&&v!=root){ //寻找有向环
//三种情况终止:找到相同标记、节点已属其他环、遍历到根
vis[v]=i;
v=pre[v];
}
if(v!=root&&id[v]==-1){ //找到环
id[v]=++cnt;
for(int u=pre[v];u!=v;u=pre[u])
id[u]=cnt;
}
}
if(cnt==0) //无环
break;
for(int i=1;i<=n;i++) //对于非环中点
if(id[i]==-1)
id[i]=++cnt;
for(int i=1;i<=m;i++){ //建新图,缩点重新标记
int 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-=dis[v];
}
n=cnt;
root=id[root];
}
return res;
}
double getdis(Pos a,Pos b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m)){
for(int i=1;i<=n;i++) //读入坐标
scanf("%lf%lf",&pos[i].x,&pos[i].y);
for(int i=1;i<=m;i++){ //建边
scanf("%d%d",&edge[i].u,&edge[i].v);
if(edge[i].u!=edge[i].v)
edge[i].w=getdis(pos[edge[i].u],pos[edge[i].v]);
else
edge[i].w=INF;
}
double res=zhuliu(1,n,m);
if(int(res)==-1)
printf("poor snoopy\n");
else
printf("%.2f\n",res);
}
return 0;
}