题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4463
题意:要在n个商店之间修路,商店p和q必须直接通过一条马路相连,所有商店之间必须能通过马路联系,求修建马路的最小长度。
分析:其实就是简单的求最小生成树问题,不过加了一个限制条件那就是点p q必须连接在一起,可在用并查集求最小生成树之前先将pq合并到一个子集中,然后在按部就班的求即可,最后一定形成了(n-1)条边,输出这(n-1)条边的长度之和即可。
反思:比赛时近30个人都写出了这道题,而我是比完赛向美丽的zj学长请教时才知道他要求的是最小生成树,可用优先队列加并查集快速求解。当我问他什么是最小生成树,什么是并查集,为什么要用优先队列时,我强烈感觉到他头顶飞过了不止n只乌鸦。为自己的无知汗颜。但是现在已经弄得基本熟悉了,我相信遇到类似算法我应该也能解决了。人生就是一个从无知到慢慢懂得一些事情,懂得多了又变得迷茫感到无知然后继续丰富充实自我的过程嘛,在这条路上我会努力前行的,因为我有要守护的人,我要成为爷爷奶奶最大的骄傲与依靠,cx加油!!!
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<map>
#include<vector>
#include<cmath>
#include<queue>
using namespace std;
typedef long long ll;
const int maxn = 10000 + 5;
struct node
{
int x,y;
}no[55];
double dis(node n1, node n2)//计算两点之间的距离
{
return sqrt(double(n1.x - n2.x)*(n1.x - n2.x) + double(n1.y - n2.y)*(n1.y - n2.y));
}
struct edge//定义边结构体储存起点 终点 及边的长度
{
int s,e;
double d;
edge(int s, int e, double d):s(s), e(e), d(d){}
bool operator<(const edge & ed) const
{ return d > ed.d; }
};
priority_queue<edge> pq;//利用优先队列将所有边按长度从小到大排好序,每次先取长度小的边
int f[55];
int Find(int x)
{
return x == f[x] ? x : f[x] = Find(f[x]);//寻找一个点的父节点
}
bool Union(int x, int y)//判断两点是否能合并
{
int fx = Find(x);
int fy = Find(y);
if(fx == fy) return false;//父节点相同,若合并则会形成圈,这样就不是最小生成树了,故不能合并
f[fx] = fy;//父节点不同,将父节点合并,返回真
return true;
}
int main()
{
int n;
while(~scanf("%d",&n) && n)
{
while(!pq.empty()) pq.pop();
memset(no, 0, sizeof(no));
for(int i = 1; i <= n; i++)
f[i] = i;//合并前每个点的父节点都是它本身
int p,q;
scanf("%d%d",&p,&q);
for(int i = 1; i <= n; i++)
{
scanf("%d%d",&no[i].x, &no[i].y);
}
for(int i = 1; i <= n; i++)
for(int j = i + 1; j <= n; j++)
{
pq.push(edge(i, j, dis(no[i], no[j])));//将各边加入优先队列中优先队列会自动排序
}
Union(p,q);//合并必须直接相连的p q两点
double sum = dis(no[p], no[q]);
while(!pq.empty())//依次从优先队列中拿出各边,若改变的两个端点能合并,则合并,否则看下一条边
{
int x = pq.top().s, y = pq.top().e;
double di = pq.top().d;
pq.pop();
if(Union(x, y))
sum += di;
}
printf("%.2lf\n",sum);//最小生成树最终一定是(n-1)条边
}
return 0;
}