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
题意:就是比较裸的一道求最小树形图的过程,借助了一位大神的很好的博客
博客链接
最小树形图的构建过程主要分为四个部分:
1、求最短弧集合E
2、判断集合E中有没有有向环,如果有转步骤3,否则转4
3、收缩点,把有向环收缩成一个点,并且对图重新构建,包括边权值的改变和点的处理,之后再转步骤1。
4、展开收缩点,求得最小树形图(大多数时候不需要展开,一般求的是最小树形图的路径和)
1.首先我们先求最短弧集合E,对于当前图如果有n个点(一个有向环的收缩点算作一个点),我们就要选出n-1个点,确定其入边的最短边,由其组成的一个集合我们就叫做最短弧集合E,如果我们枚举到某一个点的时候,它没有入边,那么说明不存在最小树形图,所以这个时候算法结束,回到主函数。
for(int i=1; i<=n; ++i)if(i!=u&&!inc[i])//u作为图的根节点,flag【i】为1的情况就是表示这个点在某个有向环里边,并且他不是这个有向环的代表点(缩点)
{
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;
}
if(pre[i] == i) return -1;//如果当前枚举到的点i没有入边,那么就不存在最小树形图(因为一颗树是要所有节点都是连通)
}
2.然后我们对集合E中的边进行判断,判断是否有有向环。刚刚的代码实现里边有一个前驱节点的存储,所以在这个部分,我们直接一直向前枚举前驱点即可,如果枚举的前驱点最终能够枚举到根节点,那么这一部分就不存在有向环,否则就存在,对于每一个点都进行向前枚举即可
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;//最后能找到起点(根)或者是走过的点已经超过了n个,表示没有有向环
break;//表示有有向环
}
3.果有有向环,我们需要对有向环进行缩点,既然我们是枚举到节点i的时候发现有有向环,我们不妨把有向环里边的点都收缩成点i。对于收缩完之后会形成一个新的图,图的变化规律是这样的(上图变换成语言描述:如果点u在环内,如果点k在环外,并且从k到u有一条边map【u】【v】=w,并且在环内还有一点i,使得map【i】【k】=w2,辣么map【k】【收缩点】=w-w2;
基于贪心思想,对于环的收缩点i和另外一点k(也在环内),对于环外一点j,如果map【k】【j】
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仍然存在
4.处理收缩点后形成的图,处理完4之后,我们就回到步骤1,继续找最小弧集E,最后找到了一个没有环的最小弧集E之后,对于没有弧的集合E中的所有边(包括能将收缩点展开的边)就是我们要求的最小树形图的边集
//收缩点的同时,对边权值进行改变
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];//map【u】【v】=w,并且在环内还有一点i,使得map【i】【k】=w2,那么map【k】【收缩点】=w-w2
}
}
题目代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
const int VN = 105;
const int INF = 0x7fffffff;
double ans; // 所求答案
int n; // 结点个数
int pre[VN]; // 权值最小的前驱边
bool vis[VN]; // 是在环中还是在环外
bool inc[VN]; // 该点是否被删除了(收缩)
double w[VN][VN];//存放权值
void init(int 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, double _w)
{
if(w[u][v]>_w) w[u][v] = _w;
}
double directed_mst(int u)//u表示根节点,该题根节点是1
{
double ans = 0;
memset(vis, 0, sizeof(vis));
while(true)
{
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])//i节点的入找最小
{
pre[i] = j;
}
if(pre[i] == i) return -1;//也可以用dfs预处理判断图的连通
}
//判断E是否有环
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;//while循环里面的意思是节点没有遍历到根,没有自环,节点没有全部遍历完,在这个条件下继续向前找
if(j==u || cnt>n) continue;//最后能找到起点(根)或者是走过的点已经超过了n个,表示没有有向环
break;
}
if(i>n)
{
for(int i=1; i<=n; ++i)if(i!=u && !inc[i]) ans+=w[pre[i]][i];
return ans;
}
//有环,进行收缩,把整个环都收缩到一个点i上。
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;
}
double x[VN],y[VN];
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))
{
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;
double W;
scanf("%d%d",&a,&b);
if(a==b)continue;
W = getDist(x[a],y[a],x[b],y[b]);
if(w[a][b]>W)
w[a][b] = W;
}
double ans = directed_mst(1);
if(ans == -1) puts("poor snoopy");//不能连通
else printf("%.2f\n", ans);//是.2f,不能是.2lf
}
return 0;
}