1. 题目分析
给定一个人的坐标 $ (x_0,y_0)$ (固定)和在这个位置上的初始磁石,以及其他 $ n$ 个剩余磁石的位置。每个磁石的信息有:位置坐标 $ (x,y) $ ,质量 $ m $ 、磁力 $ p $ 、磁力的作用半径 $ r $ 。一个磁石 $ M $ 能被这个人手上的某个磁石 $ M_0 $ 吸过来,当且仅当 $ M $ 的质量小于 $ M_0 $ 的磁力,且 $ M $ 与人的距离不大于 $ M_0$ 磁力的作用距离。磁石被吸引过来之后,可以被人自由使用,用来吸引其他可能被吸引的磁石。求所有能被人得到的磁石个数。
1.1. BFS 框架
求所有可能的结果,不难得出题目算法的总体框架是使用搜索实现,进一步地,应该使用 BFS 来实现。
1 | ans = 0; |
1.2. 分块优化
在 BFS 中,最耗时的是寻找还未被吸引并且满足被吸引条件的磁石。本题中,磁石被吸引的条件有两个:
- 被吸引磁石质量 $\leq$ 使用的磁石的磁力
- 被吸引磁石与人的距离 $\leq$ 使用的磁石的作用半径
所以需要使用数据结构维护磁石们的质量和距离序列,方便快速找到满足被队首磁石吸引条件的其他磁石。使用平衡树等数据结构比较复杂,这个时候分块就是一个比较好的选择。
把 $n$ 块磁石按照质量排序,并将序列分成 $T=\sqrt{n}$ 块。在每一块内部,再按照距离排序。这样对于每个队首的磁石 $x$ ,在序列中一定存在一个块号 $k$ ,满足:
- 块号在 $[1, k)$ 范围内的,所有磁石的质量都不大于 $x$ 的磁力 $p_x$
- 块号大于 $k$ 的,每个块内磁石的质量都大于 $p_x$
- 第 $k$ 块内,有一部分磁石的质量不大于 $p_x$
只需记录每一个块内最重磁石的质量 $maxM$ ,然后在包含 $ T$ 个 $ maxM$ 的序列中二分查找最后一个大于 $p_x$ 的 $maxM$ 所在的块号 $k$ 。然后按照上述分类,分开处理。
- 块号属于 $[1, k)$ 范围的,磁石的质量都已经满足条件。由于事先在块内已经按照距离升序排列,所以,不大于 $x$ 的作用范围 $ r_x$ 的只有各个块内的开头的连续一部分。只需要在每一块内从左边界开始暴力挨个扫描将还未被吸引去且距离满足条件的磁石入队即可。这就相当于每个块内左边连续的那一部分在经过 $ x$ 吸引后,对接下来的队列中的磁石再无关系。所以每个块内扫描到第一个大于 $r_x$ ,就可以跳出本块的循环,并将左边界设置为当前位置,这样被吸引的此时在下次就不会重复处理。二分查找的时间复杂度为 $O(\log \sqrt{n})=O(\frac{1}{2}\log n)=O(\log n)$ 。 $k$ 的上界不超过 $\sqrt{n}$ 。由于每个磁石只访问 1 次,所以扫描磁石的均摊复杂度为 $O(1)$ 。总共时间复杂度为 $O(\log n +1\cdot \sqrt{n})=O(\sqrt{n})$
- 块号大于 $k$ 的,磁石的质量已经不符合要求,不做处理。
- 第 $k$ 块内,有一部分的质量不大于 $p_x$ ,另一部分大于 $p_x$ ,并且出现的位置可能不连续。所以,必须对第 $k$ 块内部的元素从它当前的左边界挨个扫描至右边界,把所有质量和距离同时满足条件的磁石入队。注意,这里不修改左边界。时间复杂度为 $O(\sqrt{n})$ 。
所以,处理 $x$ 的时间复杂度为 $O(\sqrt{n}+\sqrt{n}) = O(\sqrt{n})$ 。BFS 总的时间复杂度为 $O(n\sqrt{n})$ 。
2. 代码实现
为了避免开方运算,距离和半径统一做平方运算。
1 | typedef long long LL; |