题目描述
在一个热带雨林中生存着一群猴子,它们以树上的果子为生。昨天下了一场大雨,现在雨过天晴,但整个雨林的地表还是被大水淹没着,部分植物的树冠露在水面上。猴子不会游泳,但跳跃能力比较强,它们仍然可以在露出水面的不同树冠上来回穿梭,以找到喜欢吃的果实。
现在,在这个地区露出水面的有N棵树,假设每棵树本身的直径都很小,可以忽略不计。我们在这块区域上建立直角坐标系,则每一棵树的位置由其所对应的坐标表示(任意两棵树的坐标都不相同)。
在这个地区住着的猴子有M个,下雨时,它们都躲到了茂密高大的树冠中,没有被大水冲走。由于各个猴子的年龄不同、身体素质不同,它们跳跃的能力不同。有的猴子跳跃的距离比较远(当然也可以跳到较近的树上),而有些猴子跳跃的距离就比较近。这些猴子非常聪明,它们通过目测就可以准确地判断出自己能否跳到对面的树上。
【问题】现已知猴子的数量及每一个猴子的最大跳跃距离,还知道露出水面的每一棵树的坐标,你的任务是统计有多少个猴子可以在这个地区露出水面的所有树冠上觅食。
输入格式
输入文件monkey.in包括:
第1行为一个整数,表示猴子的个数M(2<=M<=500);
第2行为M个整数,依次表示猴子的最大跳跃距离(每个整数值在1–1000之间);
第3行为一个整数表示树的总棵数N(2<=N<=1000);
第4行至第N+3行为N棵树的坐标(横纵坐标均为整数,范围为:-1000–1000)。
(同一行的整数间用空格分开)
输出格式
输出文件monkey.out包括一个整数,表示可以在这个地区的所有树冠上觅食的猴子数。
样例 #1
样例输入 #1
4
1 2 3 4
6
0 0
1 0
1 2
-1 -1
-2 0
2 2
样例输出 #1
3
提示
【数据规模】
对于40%的数据,保证有2<=N <=100,1<=M<=100
对于全部的数据,保证有2<=N <= 1000,1<=M=500
感谢@charlie003 修正数据
解题思路:
根据题目描述,要求其实就是使两节点之间的最大距离尽可能的小,然后统计跳跃距离不小于这个最大距离的猴子数量
也就是最小生成树
接下来介绍prim算法
prim算法的本质是贪心
基本思想:首先任意选一个节点加入树中
之后循环加入距离树最近的节点
思想很好理解吧,接下来介绍如何实现
区分加入树和没有加入树的节点很容易实现,通过一个布尔数组即可
如何获取到树的最近节点是关键
要知道到树的最近节点,就要知道每一个节点到树的最短距离
所以开一个数组维护最短距离
每加入一个新节点,尝试用该新节点去更新最短距离数组
然后从更新后的数组选出距离树最近的节点加入,如此循环即可
与常规图不同的是,这里的任意两个节点之间都可以看作相连的,但是区别也不大
本题可以算得上是prim算法的板题了
AC代码如下
//最小生成树prim
#include <iostream>
#include <algorithm>
#include <memory.h>
using namespace std;
const int max_m = 500;
const int max_n = 1000;
const int NaN = 0x3F3F3F3F;
int jump[max_m + 1] = { 0 };
struct tree { int x, y; }trees[max_n + 1];
bool book[max_n + 1] = { false };
int dist[max_n + 1] = { 0 };
int calc_dist(tree& t_1, tree& t_2) {
return (t_1.x - t_2.x) * (t_1.x - t_2.x) +
(t_1.y - t_2.y) * (t_1.y - t_2.y);
}
void prim(const int& n) {
int node = 1;
int temp = node;
dist[node] = 0;//初始化
for (int i = 1; i <= n; i++) {//最小生成树
int min_dist = NaN;//初始化
for (int j = 1; j <= n; j++) {
if (!book[j]) {//未加入最小生成树
dist[j] = min(dist[j], calc_dist(trees[node], trees[j]));//更新到树的最小距离
if (dist[j] < min_dist) {
temp = j;
min_dist = dist[j];
}
}
}
node = temp;
book[node] = true;//加入最小生成树
}
}
int main() {
memset(dist, 0x3F, sizeof(int) * (max_n + 1));//初始化
int m, n, x, y;
cin >> m;
for (int i = 1; i <= m; i++) cin >> jump[i];
cin >> n;
for (int i = 1; i <= n; i++) {//存图
cin >> x >> y;
trees[i] = { x,y };
}
prim(n);
sort(dist + 1, dist + n + 1);
sort(jump + 1, jump + m + 1);
int i;
for (i = 1; i <= m; i++) {
if (jump[i] * jump[i] >= dist[n]) break;
}
cout << m - i + 1;
return 0;
}
这里由于为了避免开根号带来的精度误差,选择存储平方值~~(反正不会溢出)~~
如果学习过Dijkstra算法,这里再说明一下prim和Dijkstra的不同之处
Dijkstra是要求从一个节点到达所有节点的路径最短,并不关心每条边的长度
prim是要求每条边最短,并不关心最短路径
举一个简单的例子
现在有 10 10 10个节点按顺序围成一个环,除了 1 1 1到 10 10 10的距离为 5 5 5,每两个节点之间的距离都是 1 1 1
现在如果是Dijkstra,获取 1 1 1到 10 10 10的距离应该为 5 5 5,
而prim生成的最小生成树中这个距离为 9 9 9