Freckles
Time Limit: 1000MS | Memory Limit: 65536K | |
Description
In an episode of the Dick Van Dyke show, little Richie connects the freckles on his Dad's back to form a picture of the Liberty Bell. Alas, one of the freckles turns out to be a scar, so his Ripley's engagement falls through.
Consider Dick's back to be a plane with freckles at various (x,y) locations. Your job is to tell Richie how to connect the dots so as to minimize the amount of ink used. Richie connects the dots by drawing straight lines between pairs, possibly lifting the pen between lines. When Richie is done there must be a sequence of connected lines from any freckle to any other freckle.
Consider Dick's back to be a plane with freckles at various (x,y) locations. Your job is to tell Richie how to connect the dots so as to minimize the amount of ink used. Richie connects the dots by drawing straight lines between pairs, possibly lifting the pen between lines. When Richie is done there must be a sequence of connected lines from any freckle to any other freckle.
Input
The first line contains 0 < n <= 100, the number of freckles on Dick's back. For each freckle, a line follows; each following line contains two real numbers indicating the (x,y) coordinates of the freckle.
Output
Your program prints a single real number to two decimal places: the minimum total length of ink lines that can connect all the freckles.
Sample Input
3 1.0 1.0 2.0 2.0 2.0 4.0
Sample Output
3.41
Source
我的理解:
1.Prim算法:
将原图的所有点分为两个点集A和B,随意选择一个点压入点集A中,用一个数组d[ ],使d[i]表示点集A内的所有点分别到 i 点的距离中的最小值(松弛操作),然后每次再从点集B里选择出当前d[ ]中最小的边,将边的终点压入点集A,重复松弛操作,再循环从B中选择点加入A中。直到所有点都被压入了点集A中。
区分A,B点集,就可以用v数组标记。
/*
题意:n个点,用坐标表示,让你用一条连续的折线段将所有的点连起来,输出折线段的最短长度。
分析:求最小生成树。边集是n个点组成的完全图的所有边,本来对于稠密图,prim算法应该是更加适用,可是本题范围只是100,所以kruskal算法也不会超时。
*/
// prim
//hdu 1162(AC)poj 2560(AC)
#include<stdio.h>
#include<math.h>
#include<string.h>
using namespace std;
#define MAX 999999999.0
struct point{
double x,y;
}m[102];
double d[101];//
double dd[101][101];//记录每两点之间的距离
int v[101];//标记是否被选择进入点集A
int n;
double dis(point a,point b)//求距离
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
void init()
{
memset(v,0,sizeof(v));
for (int i = 1; i <= n; i++)
{
for (int j = i+1; j <= n; j++)
{
dd[i][j] = dd[j][i] = dis(m[i],m[j]);//求距离
}
dd[i][i] = 0.0;
}
}
int main()
{
int i,j,k;
double ans;
while(scanf("%d",&n)!=EOF)
{
for(i=1;i<=n;i++)
scanf("%lf %lf",&m[i].x,&m[i].y);
init();
ans = 0.0;
for (int i = 1; i <= n; i++)
{
d[i] = dd[1][i];//选取1号节点先放到被选择的点集A里。
}
while(1)
{
j = -1;
double min = MAX;
for (int i = 1; i <= n; i++)
{
if(!v[i] && d[i]<min)//从已选择的点集里的一点x开始遍历,找到与x相距最近的一点j。
{
j = i;
min = d[i];
}
}
if(j == -1)//当所有点都在点集A里时,最小生成树已经找到。
{
printf("%.2lf\n",ans);break;//lf...f...
}
v[j] = 1;//将j加入点集A
ans += min;
for (int i = 1; i <= n; i++)
{
if(!v[i] && d[i]>dd[j][i])//松弛操作,使d[i]表示点集A内的点到i点的最短距离。
d[i] = dd[j][i];
}
}
}
return 0;
}
2.Kruskal算法
将原图的所有边分为两个集合A,B,不断从B中挑选边加入A中,直到所有点都被遍历过。具体实现用到了并查集这样的数据结构。用于判断加入的边是否构成的回路。因为非负最小生成树里是不会是存在回路的。挑选边的时候是从最小往最大的添加,只要没有回路就添加,知道所有点被遍历过。
//kruskal
#include<stdio.h>
#include<math.h>
#include<algorithm>
#include<string.h>
using namespace std;
#define MAX 999999999.0
struct point
{
double x,y;
}m[102];
struct edge
{
int a,b;
double len;
}e[5500];
int n,num;
int p[101];
double dis(point a,point b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
void init()
{
for (int i = 1; i <= n; i++)
p[i] = i;
for (int i = 1; i <= n; i++)
{
for (int j = i+1; j <= n; j++)
{
e[num].a = i;//记录每一条边
e[num].b = j;
e[num].len = dis(m[i],m[j]);
num++;
}
}
}
int root(int x)//寻根
{
if(x==p[x])return x;
return p[x]=root(p[x]);
}
void merge(int a,int b)//合并
{
a = root(a);
b = root(b);
p[a] = b;
}
bool cmp(edge a,edge b)
{
return a.len<b.len;
}
int main()
{
int i,j,k,a,b;
double ans;
while(scanf("%d",&n)!=EOF)
{
for(i=1;i<=n;i++)
scanf("%lf %lf",&m[i].x,&m[i].y);
num = 0;
init();
ans = 0.0;
sort(e,e+num,cmp);//排序
int flag = 0;//记录加入的边数
for (int i = 0; i < num && flag<n-1; i++)//小优化,不用遍历所有边,当加入的边数达到n-1,就可以停止了。
{
a = root(e[i].a);
b = root(e[i].b);
if(a!=b)
{
merge(a,b);
ans+=e[i].len;
flag ++;
}
}
printf("%.2lf\n",ans);//G++里面要这样写printf("%.2f\n",ans);不能用lf只能用f;
//在c++里面可以用.lf。。。。。。。。。。。。
}
return 0;
}
需要注意的是 G++和C++的标准不同,对于double型数字的处理也不同,在G++编译环境下不可以用%.xlf来取x位小数,会出错,会WA。。。换成%.xf就可以了。在C++里要用%.xlf。
另:看网上有些人说到,kruskal算法显然会比较快(因为它只对边进行了一次完整的遍历,prim要每加一个点都要遍历边进行松弛),而且随着以后对排序算法的不断优化,kruskal和prim的差异会越加明显,个人觉得后者还比较靠谱。