小明最近迷上了一款名为《扫雷》的游戏。其中有一个关卡的任务如下, 在一个二维平面上放置着 n 个炸雷,第 i 个炸雷 (xi , yi ,ri) 表示在坐标 (xi , yi) 处存在一个炸雷,它的爆炸范围是以半径为 ri 的一个圆。
为了顺利通过这片土地,需要玩家进行排雷。玩家可以发射 m 个排雷火箭,小明已经规划好了每个排雷火箭的发射方向,第 j 个排雷火箭 (xj , yj ,rj) 表示这个排雷火箭将会在 (xj , yj) 处爆炸,它的爆炸范围是以半径为 rj 的一个圆,在其爆炸范围内的炸雷会被引爆。同时,当炸雷被引爆时,在其爆炸范围内的炸雷也会被引爆。现在小明想知道他这次共引爆了几颗炸雷?
你可以把炸雷和排雷火箭都视为平面上的一个点。一个点处可以存在多个炸雷和排雷火箭。当炸雷位于爆炸范围的边界上时也会被引爆。
输入格式
输入的第一行包含两个整数 n、m.
接下来的 n 行,每行三个整数 xi , yi ,ri,表示一个炸雷的信息。
再接下来的 m 行,每行三个整数 xj , yj ,rj,表示一个排雷火箭的信息。
输出格式
输出一个整数表示答案。
样例输入
2 1
2 2 4
4 4 2
0 0 5
样例输出
2
提示
示例图如下,排雷火箭 1 覆盖了炸雷 1,所以炸雷 1 被排除;炸雷 1 又覆盖了炸雷 2,所以炸雷 2 也被排除。

对于 40% 的评测用例:0 ≤ x, y ≤ 1e9 , 0 ≤ n, m ≤ 1e3 , 1 ≤ r ≤ 10.
对于 100% 的评测用例:0 ≤ x, y ≤ 1e9 , 0 ≤ n, m ≤ 5 × 1e4 , 1 ≤ r ≤ 10.
这个问题是一个典型的圆覆盖与连锁反应 问题,本质上可以抽象为图的遍历(DFS/BFS)。
核心挑战
-
连锁反应:火箭引爆炸雷,炸雷再引爆炸雷。这构成了一个有向图的搜索问题。
-
坐标范围巨大:x, y高达 10^9,无法使用二维数组。
-
圆心距离计算:判断点 (x1, y1) 是否在半径为 r 的圆 (x0, y0, r) 内,公式为:
(x1-x0)^2+(y1-y0)^2<=r^2
-
爆炸半径很小 :注意到提示中 r ≤ 10.,这是一个非常关键的突破口。
优化思路:哈希表 + 邻域搜索
由于坐标极大但半径很小,我们可以将所有炸雷的坐标通过**哈希(Hash)**存储。
-
存储 :使用
std::map或std::unordered_map将坐标(x, y)映射到该位置上的炸雷信息(记录半径和数量)。为了处理哈希冲突和坐标组合,通常将x和y转化成一个唯一键,例如key = x * OFFSET + y。 -
搜索范围 :对于一个爆炸圆 (x, y, r),我们不需要遍历所有 n个炸雷。由于 r ≤ 10.,我们只需要搜索 x 坐标在 [x-r, x+r] 且 y 坐标在 [y-r, y+r] 范围内的点即可。
-
去重:一旦某个坐标上的炸雷被引爆,就标记为已访问,防止死循环。
#include <iostream>
#include <vector>
#include <unordered_map>
#include <cmath>
using namespace std;
typedef long long LL;
// 炸雷结构体
struct Mine {
int x, y, r, num;
bool visited;
};
// 哈希函数,将坐标映射为唯一 key
const LL OFFSET = 1000000001LL;
unordered_map<LL, int> pos_to_id;
vector<Mine> mines;
// 距离平方计算
LL get_dist_sq(LL x1, LL y1, LL x2, LL y2) {
return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
}
int ans = 0;
void dfs(int id) {
mines[id].visited = true;
ans += mines[id].num;
LL x = mines[id].x;
LL y = mines[id].y;
LL r = mines[id].r;
// 只搜索半径范围内的格子
for (LL i = x - r; i <= x + r; i++) {
for (LL j = y - r; j <= y + r; j++) {
LL key = i * OFFSET + j;
if (pos_to_id.count(key)) {
int next_id = pos_to_id[key];
if (!mines[next_id].visited && get_dist_sq(x, y, i, j) <= r * r) {
dfs(next_id);
}
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
for (int i = 0; i < n; i++) {
int x, y, r;
cin >> x >> y >> r;
LL key = (LL)x * OFFSET + y;
if (pos_to_id.count(key)) {
int id = pos_to_id[key];
mines[id].r = max(mines[id].r, r); // 同一位置取半径最大的
mines[id].num++;
} else {
pos_to_id[key] = mines.size();
mines.push_back({x, y, r, 1, false});
}
}
for (int i = 0; i < m; i++) {
int x, y, r;
cin >> x >> y >> r;
// 火箭引爆炸雷
for (LL i_x = x - r; i_x <= x + r; i_x++) {
for (LL j_y = y - r; j_y <= y + r; j_y++) {
LL key = i_x * OFFSET + j_y;
if (pos_to_id.count(key)) {
int id = pos_to_id[key];
if (!mines[id].visited && get_dist_sq(x, y, i_x, j_y) <= (LL)r * r) {
dfs(id);
}
}
}
}
}
cout << ans << endl;
return 0;
}