试题 H: 扫雷

题目链接:

点击跳转

题目描述:

小明最近迷上了一款名为《扫雷》的游戏。

其中有一个关卡的任务如下:

在一个二维平面上放置着 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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值