[蓝桥杯 2022 省 B] 扫雷
题目描述
小明最近迷上了一款名为《扫雷》的游戏。其中有一个关卡的任务如下,在一个二维平面上放置着 n n n 个炸雷,第 i i i 个炸雷 ( x i , y i , r i ) \left(x_{i}, y_{i}, r_{i}\right) (xi,yi,ri) 表示在坐标 ( x i , y i ) \left(x_{i}, y_{i}\right) (xi,yi) 处存在一个炸雷,它的爆炸范围是以半径为 r i r_{i} ri 的一个圆。
为了顺利通过这片土地,需要玩家进行排雷。玩家可以发射 m m m 个排雷火箭,小明已经规划好了每个排雷火箭的发射方向,第 j j j 个排雷火箭 ( x j , y j , r j ) \left(x_{j}, y_{j}, r_{j}\right) (xj,yj,rj) 表示这个排雷火箭将会在 ( x j , y j ) \left(x_{j}, y_{j}\right) (xj,yj) 处爆炸,它的爆炸范围是以半径为 r j r_{j} rj 的一个圆,在其爆炸范围内的炸雷会被引爆。同时,当炸雷被引爆时,在其爆炸范围内的炸雷也会被引爆。现在小明想知道他这次共引爆了几颗炸雷?
你可以把炸雷和排雷火箭都视为平面上的一个点。一个点处可以存在多个炸雷和排雷火箭。当炸雷位于爆炸范围的边界上时也会被引爆。
输入格式
输入的第一行包含两个整数 n n n、 m m m。
接下来的 n n n 行, 每行三个整数 x i , y i , r i x_{i}, y_{i}, r_{i} xi,yi,ri, 表示一个炸雷的信息。
再接下来的 m m m 行,每行三个整数 x j , y j , r j x_{j}, y_{j}, r_{j} xj,yj,rj, 表示一个排雷火箭的信息。
输出格式
输出一个整数表示答案。
样例 #1
样例输入 #1
2 1
2 2 4
4 4 2
0 0 5
样例输出 #1
2
提示
【样例说明】
示例图如下, 排雷火箭 1 覆盖了炸雷 1 , 所以炸雷 1 被排除; 炸雷 1 又覆 盖了炸雷 2 , 所以炸雷 2 也被排除。
【评测用例规模与约定】
对于 40 % 40 \% 40% 的评测用例: 0 ≤ x , y ≤ 1 0 9 , 0 ≤ n , m ≤ 1 0 3 , 1 ≤ r ≤ 10 0 \leq x, y \leq 10^{9}, 0 \leq n, m \leq 10^{3}, 1 \leq r \leq 10 0≤x,y≤109,0≤n,m≤103,1≤r≤10.
对于 100 % 100 \% 100% 的评测用例: 0 ≤ x , y ≤ 1 0 9 , 0 ≤ n , m ≤ 5 × 1 0 4 , 1 ≤ r ≤ 10 0 \leq x, y \leq 10^{9}, 0 \leq n, m \leq 5 \times 10^{4}, 1 \leq r \leq 10 0≤x,y≤109,0≤n,m≤5×104,1≤r≤10.
蓝桥杯 2022 省赛 B 组 H 题。
思路
首先想到可以根据射程范围建立
以排雷火箭为起点的有向图
但是本题的数据范围0<=x,y<=10^9
直接枚举每个火箭和所有炸雷的情况,时间复杂度为n^2,会超时
在本题中r<=10
所以想到可以直接枚举每个排雷火箭在他射程范围之内的炸雷爆炸情况
由于这样是遍历每个坐标点,我们如何通过坐标点映射到炸雷的编号
可以用哈希的做法
将坐标点与炸雷编号联系起来
在判断完每个排雷火箭射程范围内可以引爆的炸雷后
利用bfs函数或者dfs函数
再进行判断某一炸雷被引爆后得连锁反应
判断是否在排雷火箭或炸雷的射程范围内
可以先枚举出2r*2r的矩形
最后再判断是否在圆内
最后将所有被炸的炸雷加起来即可
手写哈希表
本题如果利用c++中stl中unordered_map函数很容易卡常数
所以不得不手写哈希算法
开几个数组
h[] 将(x,y)转化成一个哈希值(利用哈希函数)
id[] 将哈希值与炸雷编号关联起来
st[] 判断每个雷是否被引爆过
哈希表的长度:题目已知n是小于等于5e4的,我们的哈希表至少是2n,
但是应该尽可能的开大,这样就可以避免冲突的发生,同
时,哈希表的长度为质数时最好!!我们这里取一个M = 1e6+7。
哈希表的key:获取哈希值以后,我们需要将其存入哈希表的一个位置,
这个位置就是该哈希值的key。
我们将得到的哈希值对哈希表长度取模即可得到,
但是如果发生冲突需要找到其后第一个空闲的位置。
哈希表的value:哈希值
h[key]=value
id[key]=炸雷的编号
本题中一个点处可以存在多个炸雷和排雷火箭
所以如果出现多个点
那么只保留射程范围大的炸雷
代码
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
//每个点数组范围,哈希数组范围,
const int N=5e4+10,M=1e6+7,X=1e9+1;
int n,m;
struct node
{
int x,y,r;
}b[N];
typedef long long ll;
ll h[M];//哈希数组
bool st[M];//判断该雷是否被炸
int id[M],res;//哈希数组位置上的雷的编号
//得到每个坐标的哈希值
ll get_hs(int x,int y)
{
return ll(x*X+y);//哈希函数
}
//找到该坐标被哈希数组存储的下标key
int find(int x,int y)
{
ll hs=get_hs(x,y);
int key=(hs%M+M)%M;
//如果该位置已经被占用并且不等于我们要求的哈希值
//在之后找它的位置
while(h[key]!=-1&&h[key]!=hs)
{
key++;
if(key==M) key=0;//走到哈希表末尾,从头开始
}
return key;
}
//判断该位置的雷是否在导弹圆圈内
bool check(int x1,int y1,int r,int x,int y)
{
int d=(x1-x)*(x1-x)+(y1-y)*(y1-y);
return d<=r*r;
}
void bfs(int pos)
{
queue<int> q;
q.push(pos);
st[pos]=true;
while(q.size())
{
int t=q.front();
q.pop();
int x=b[t].x,y=b[t].y,r=b[t].r;
for(int xx=x-r;xx<=x+r;xx++)
for(int yy=y-r;yy<=y+r;yy++)
{
int key=find(xx,yy);
if(id[key]&&!st[id[key]]&&check(x,y,r,xx,yy))
{
int pos=id[key];
st[pos]=1;
q.push(pos);
}
}
}
}
int main()
{
cin>>n>>m;
memset(h,-1,sizeof(h));
int x,y,r;
//读入地雷,存入哈希表
for(int i=1;i<=n;i++)
{
cin>>x>>y>>r;
b[i]={x,y,r};
int key=find(x,y);//找到该坐标点对应的哈希下标
if(h[key]==-1) h[key]=get_hs(x,y);//如果哈希表此位置为空,则插入
//id数组没有被标记或者找到了同一坐标点更大半径的地雷
//只保留半径更大地雷
if(!id[key]||b[id[key]].r<r)
id[key]=i;
}
//读入排雷火箭
for(int i=1;i<=m;i++)
{
cin>>x>>y>>r;
//枚举导弹构成矩形范围内是否有雷
for(int xx=x-r;xx<=x+r;xx++)
for(int yy=y-r;yy<=y+r;yy++)
{
int key=find(xx,yy);
//如果该点有雷没有被炸且在导弹的圆范围内,则地雷被炸,并且bfs引爆新的地雷
if(id[key]&&!st[id[key]]&&check(x,y,r,xx,yy)) bfs(id[key]);
}
}
//遍历每个地雷,看是否被标记
for(int i=1;i<=n;i++)
{
int key=find(b[i].x,b[i].y);//获得坐标值对应哈希表的下标
int pos=id[key];//获得哈希表位置上存放的地雷编号
if(pos&&st[pos]) res++;
}
cout<<res<<endl;
return 0;
}