题目链接:
题目描述:
小明最近迷上了一款名为《扫雷》的游戏。
其中有一个关卡的任务如下:
在一个二维平面上放置着 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,表示一个排雷火箭的信息。
输出格式 输出一个整数表示答案。
数据范围
对于 40% 的评测用例:0≤x,y≤109,0≤n,m≤103,1≤r≤10,
对于 100% 的评测用例:0≤x,y≤109,0≤n,m≤5×104,1≤r≤10。
输入样例:
2 1
2 2 4
4 4 2
0 0 5
输出样例:
2
样例解释:
示例图如下,排雷火箭 1 覆盖了炸雷 1,所以炸雷 1 被排除;炸雷 1 又覆盖了炸雷 2,所以炸雷 2 也被排除。
题目分析:
- 分析:题目很清晰,就是一个火箭可以的爆炸范围了的炸弹全部都会被引爆,被引爆的炸弹又会引爆其他炸弹,最暴力的就是每一个火箭都去枚举一次所有的炸弹,如果该炸弹会被引爆,那么再用该炸弹去枚举所有炸弹,检查是否能引爆其他炸弹。时间复杂度是O(
n
3
{n^3}
n3),很明显是过不了的,那么可以优化一下,先用邻接表把每个炸弹能引爆的其他炸弹存下来,那么遍历该炸弹是否会引爆其他炸弹的时候,总共之后遍历
n
次,那么时间复杂度就变成了 O( n 2 {n^2} n2),但是还是过不了 - 优化:可以发现题目中的一个关键的点,火箭的爆炸范围只有 r (r <= 10),那么一个火箭最多只会引爆身边范围的 100 个点,那么根本就不需要去枚举每一个炸弹,而只需要枚举 身边的 100 个点,看是否存在炸弹,就行了。再用炸弹枚举身边是否存在炸弹,以此类推就可以了。这样时间复杂度就降下来了。
算法1:
哈希+建图 – 时间复杂度O(100*n):
代码:
/*
* @Author: suhuamo
* @Date: 2022-04-13 15:23:54
* @LastEditTime: 2022-04-15 22:43:53
* @FilePath: \algorithm\蓝桥杯\第十三届蓝桥杯省赛B\H.cpp
* @slogan: 也许散落在浩瀚宇宙的小行星们也知道
* 知识点: 哈希+建图
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 5e4 + 10, M = 1e6 + 7, X = 1e9 + 1;
struct node
{
int x, y, r;
}boom[N];
int n, m;
/** hs 是 hash 数组*/
LL hs[M];
/** id 代表对应的 hash 数组的这个点是哪个炸弹 */
int id[M];
/** st 代表对应的 hash 数组的这个点是否炸了 */
bool st[M];
LL get_hs(int x, int y)
{
return (LL)x * X + y;
}
int find(int x, int y)
{
LL hs_k = get_hs(x, y);
int k = (hs_k % M + M) % M;
while(hs[k] != -1 && hs[k] != hs_k)
{
k++;
if(k == M) k = 0;
}
return k;
}
bool check(int x1, int y1, int r, int x2, int y2)
{
LL d = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
// 如果炸得到
if(d <= (LL)r * r) return true;
return false;
}
void bfs(int x, int y)
{
int k = find(x, y);
queue<int> q;
q.push(k);
st[k] = true;
while(!q.empty())
{
int k = q.front();
q.pop();
int i = id[k];
int r = boom[i].r, x = boom[i].x, y = boom[i].y;
for(int x1 = x - r; x1 <= x + r; x1++)
for(int y1 = y - r; y1 <= y + r; y1++)
{
k = find(x1, y1);
if(id[k] && !st[k] && check(x, y, r, x1, y1))
{
q.push(k);
st[k] = true;
}
}
}
}
int main()
{
memset(hs, -1, sizeof hs);
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++)
{
int x, y, r;
scanf("%d%d%d", &x, &y, &r);
boom[i] = {x, y, r};
// 进行hash映射
int k = find(x, y);
if(hs[k] == -1) hs[k] = get_hs(x, y);
// 如果这个位置第一次出现点,那么保存||如果这个点出现了两个炸弹,那么取半径最大的那个炸弹就可以了
if(id[k] == 0 || boom[id[k]].r < r) id[k] = i;
}
for(int i = 0; i < m; i++)
{
int x, y, r;
scanf("%d%d%d", &x, &y, &r);
for(int x1 = x - r; x1 <= x + r; x1++)
for(int y1 = y - r; y1 <= y + r; y1++)
{
int k = find(x1, y1);
// 如果该点有雷,并且没被炸过,并且可以被炸
if(id[k] && !st[k] && check(x, y, r, x1, y1)) bfs(x1, y1);
}
}
int res = 0;
for(int i = 1; i <= n; i++)
{
int k = find(boom[i].x, boom[i].y);
// 如果这个地方被炸了
if(st[k]) res++;
}
printf("%d\n",res);
return 0;
}