[HAOI2006] 聪明的猴子
题目描述
在一个热带雨林中生存着一群猴子,它们以树上的果子为生。昨天下了一场大雨,现在雨过天晴,但整个雨林的地表还是被大水淹没着,部分植物的树冠露在水面上。猴子不会游泳,但跳跃能力比较强,它们仍然可以在露出水面的不同树冠上来回穿梭,以找到喜欢吃的果实。
现在,在这个地区露出水面的有 棵树,假设每棵树本身的直径都很小,可以忽略不计。我们在这块区域上建立直角坐标系,则每一棵树的位置由其所对应的坐标表示(任意两棵树的坐标都不相同)。
在这个地区住着的猴子有 个,下雨时,它们都躲到了茂密高大的树冠中,没有被大水冲走。由于各个猴子的年龄不同、身体素质不同,它们跳跃的能力不同。有的猴子跳跃的距离比较远(当然也可以跳到较近的树上),而有些猴子跳跃的距离就比较近。这些猴子非常聪明,它们通过目测就可以准确地判断出自己能否跳到对面的树上。
现已知猴子的数量及每一个猴子的最大跳跃距离,还知道露出水面的每一棵树的坐标,你的任务是统计有多少个猴子可以在这个地区露出水面的所有树冠上觅食。
输入格式
输入包括:
第 行为一个整数,表示猴子的个数 ;
第 行为 个整数,依次表示猴子的最大跳跃距离(每个整数值在 之间);
第 行为一个整数表示树的总棵数 ;
第 行至第 行为 棵树的坐标(横纵坐标均为整数,范围为:)。
(同一行的整数间用空格分开)
输出格式
输出包括一个整数,表示可以在这个地区的所有树冠上觅食的猴子数。
输入输出样例
输入样例 #1
4
1 2 3 4
6
0 0
1 0
1 2
-1 -1
-2 0
2 2
输出样例 #1
3
说明/提示
对于 的数据,保证有 ,
对于全部的数据,保证有 ,
感谢 @charlie003 修正数据
题解
1 篇题解
登录后即可使用 Markdown 发布题解。
登录 / 注册YuyiLv.6👑 站长管理员P2504 [HAOI2006] 聪明的猴子 题解 Author: Yuyi 题意分析 猴子想要在所有的树冠上觅食,就意味着它必须要能在所有树之间来回穿梭。换句话说,对于这只猴子而言,它能够到达的那些树必须构成一个 完整的连通图 。 猴子能跳跃的最大距离是固定的,如果这个最大距离大于等于将所有树连通所需的 最小的“最大跳跃跨度” ,那么这只猴子就能存活。 这…点击收起题解
P2504 [HAOI2006] 聪明的猴子 题解
Author: Yuyi
题意分析
猴子想要在所有的树冠上觅食,就意味着它必须要能在所有树之间来回穿梭。换句话说,对于这只猴子而言,它能够到达的那些树必须构成一个完整的连通图。 猴子能跳跃的最大距离是固定的,如果这个最大距离大于等于将所有树连通所需的最小的“最大跳跃跨度”,那么这只猴子就能存活。 这不就是赤裸裸的求瓶颈生成树(最小生成树中的最大边权)吗! 只要我们用 Kruskal 算法求出使得这 棵树连通的最小生成树,并记录下生成树里最长的那条边的长度 。所有跳跃距离 的猴子就都是可以在所有树冠上觅食的“聪明的猴子”。
细节优化:全整数避开精度大坑
算两点间的欧几里得距离要开根号,这会引入浮点数 double,而浮点数在边界比较时极易产生精度误差导致 WA。
由于题目给定的坐标以及猴子的跳跃距离全都是整数,我们可以极其优雅地做一个降维打击:不开根号,全员比较距离的平方!
- 构建完全图时,边权直接存
(x1-x2)^2 + (y1-y2)^2。 - Kruskal 跑出来的瓶颈边权
ans也就是一个距离的平方值,且一定是纯整数。 - 最后统计的时候,把猴子的跳跃距离平方一下(
d[i] * d[i]),直接和ans进行整数大小比较。一击绝杀,极其精准!
正解代码及分析
#include <bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
#define eps 1e-9
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define dep(i,a,b) for(int i=(a);i>=(b);i--)
#define lowbit(a) (a)&(-a)
using namespace std;
const int maxn = 500005; // 按照完全图最大边数 N(N-1)/2 约50万统一调配
const int mo = 998244353;
const double pi = acos(-1.0);
template <typename T>
inline void read(T &X)
{
X = 0;int w = 0; char ch = 0;
while(!isdigit(ch)) {w |= ch == '-' ;ch = getchar();}
while(isdigit(ch)) X = X * 10 + (ch^48),ch = getchar();
if(w) X = -X;
}
int m,n;
int d[maxn];
int x[maxn],y[maxn];
struct Edge
{
int u,v;
int w; // w 存储的是距离的平方,使用 int 即可(坐标差不超过2000,平方400万不溢出)
}e[maxn];
bool cmp(Edge A,Edge B)
{
return A.w < B.w;
}
int fa[maxn];
int find(int u)
{
if(u == fa[u]) return u;
return fa[u] = find(fa[u]);
}
void solve()
{
read(m);
rep(i,1,m) read(d[i]);
read(n);
rep(i,1,n) read(x[i]),read(y[i]);
int tot = 0;
rep(i,1,n)
{
rep(j,i+1,n)
{
tot++;
e[tot].u = i;
e[tot].v = j;
e[tot].w = (x[i]-x[j])*(x[i]-x[j]) + (y[i]-y[j])*(y[i]-y[j]);
}
}
rep(i,1,n) fa[i] = i;
sort(e+1,e+1+tot,cmp);
int cnt = 0;
int ans = 0; // 记录生成树中的最大边权(平方值)
rep(i,1,tot)
{
int fu = find(e[i].u);
int fv = find(e[i].v);
if(fu != fv)
{
fa[fu] = fv;
ans = max(ans,e[i].w);
cnt++;
}
if(cnt == n-1) break; // 连通N个点只需要N-1条边
}
int res = 0;
rep(i,1,m)
{
if(d[i]*d[i] >= ans) res++; // 猴子跳跃距离的平方大于等于所需边权平方即为合格
}
cout << res << endl;
return;
}
int main()
{
int T=1;
// freopen("mul.in","r",stdin);
// freopen("mul.out","w",stdout);
// read(T);
while(T--) solve();
return 0;
}
复杂度分析
- 时间复杂度:共有 棵树,预处理并计算所有树之间的距离平方耗费 。此时总边数
tot约为 ,对其排序耗时 。Kruskal 合并过程配合并查集的路径压缩,耗时也是 。最后遍历 个猴子统计合格数量耗时 。整体时间复杂度为 ,不到 1000 万次运算,秒杀级别。 - 空间复杂度:由于不使用浮点数,存储边权只需 的
int。边集最大规模约 50万,严格遵守开辟规则,包括原本非常小的主数组d,x,y和并查集数组fa,统统一刀切使用maxn = 500005进行开辟,完美杜绝写死数字的风险,总内存开销不到 10MB,空间复杂度极度稳健,锁定为 。
讨论
0 条讨论
登录后即可使用 Markdown 发起讨论和回复。
登录 / 注册还没有讨论,来发起第一条吧。