题解:洛谷 P2280 激光炸弹

【题目来源】

洛谷:P2280 [HNOI2003] 激光炸弹 - 洛谷

【题目描述】

一种新型的激光炸弹,可以摧毁一个边长为 m 的正方形内的所有目标。现在地图上有 \(n\) 个目标,用整数 \(x_i\) , \(y_i\) 表示目标在地图上的位置,每个目标都有一个价值 \(v_i\)。激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆破范围,即那个边长为 \(m\) 的边必须与 \(x\) 轴,\(y\) 轴平行。若目标位于爆破正方形的边上,该目标不会被摧毁。

现在你的任务是计算一颗炸弹最多能炸掉地图上总价值为多少的目标。

可能存在多个目标在同一位置上的情况。

【输入】

输入的第一行为整数 \(n\) 和整数 \(m\)

接下来的 \(n\) 行,每行有 \(3\) 个整数 \(x, y, v\),表示一个目标的坐标与价值。

【输出】

输出仅有一个正整数,表示一颗炸弹最多能炸掉地图上总价值为多少的目标(结果不会超过 \(32767\) )。

【输入样例】

2 1
0 0 1
1 1 1

【输出样例】

1

【核心思想】

  1. 问题分析:在地图上有 \(n\) 个目标,每个目标有坐标 \((x_i, y_i)\) 和价值 \(v_i\)。可投放一枚边长为 \(m\) 的正方形炸弹(边与坐标轴平行),炸弹会摧毁正方形内部(包括边界)的所有目标。需要计算这颗炸弹最多能摧毁的总价值。由于地图坐标范围不大(通常不超过 \(5000\)),直接枚举所有可能的正方形位置并求内部价值和会超时,需用二维前缀和预处理,使查询任意矩形区域的价值和达到 \(O(1)\)

  2. 算法选择

    • 二维前缀和:将目标坐标整体右移 \(1\)(避免下标 \(0\) 导致边界处理困难),构建二维数组 \(a[x][y]\) 记录该坐标上的价值和(同一位置可能有多个目标,需累加)。然后计算二维前缀和 \(s[i][j] = \sum_{x=1}^{i} \sum_{y=1}^{j} a[x][y]\)
    • 枚举正方形位置:遍历所有可能的正方形右下角 \((i, j)\)\(i,j \ge m\)),利用前缀和 \(O(1)\) 计算以 \((i-m+1, j-m+1)\) 为左上角、\((i, j)\) 为右下角的 \(m \times m\) 区域的总价值,取最大值。
  3. 关键步骤

    • 读入与坐标偏移:读入 \(n\)\(m\),将每个目标的坐标 \(x, y\)\(1\)(变成 \(1\)-based),在数组 \(a[x][y]\) 上累加价值 \(v\)
    • 计算二维前缀和:递推公式 \(s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j]\),得到任意 \([1..i] \times [1..j]\) 区域的和。
    • 枚举右下角并更新答案:令 \(maxX, maxY\) 为所有目标坐标的最大值(至少为 \(m\),确保遍历到边界)。对于 \(i\)\(m\)\(maxX\)\(j\)\(m\)\(maxY\),计算当前正方形价值和:
      \(cur = s[i][j] - s[i-m][j] - s[i][j-m] + s[i-m][j-m]\)
      \(cur\) 更新答案 \(ans\)
    • 输出\(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(C^2)\),其中 \(C\) 为坐标范围(通常取 \(5005\)),预处理 \(O(C^2)\),枚举 \(O(C^2)\),完全可行。
    • 空间复杂度:\(O(C^2)\),用于存储二维数组和前缀和。
  5. 二维前缀和的核心思想

    • 降维加速:通过预处理所有矩形区域的和,使任意子矩阵的查询降为常数时间,避免了每次重新累加。
    • 包容性:将坐标整体右移 \(1\) 可统一处理下标 \(0\) 的边界,避免判断越界。
    • 重复累加:同一位置可能有多个目标,直接累加值即可。
    • 最大化覆盖:炸弹的正方形边长为 \(m\),且坐标离散化后,覆盖范围为连续的 \(m \times m\) 个点(包含边界),因此枚举所有可能的右下角等价于枚举所有位置。
    • 适用于静态二维矩阵中多次询问或求固定大小子矩阵和最大值的场景。

【解题思路】

【算法标签】

普及 #前缀和

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 5005;          // 定义二维数组最大尺寸
int n, m;                     // n: 点数, m: 正方形边长
int a[N][N];                  // 原始值数组
int s[N][N];                  // 二维前缀和数组

int main()
{
    // 输入点数和正方形边长
    cin >> n >> m;

    // 输入每个点的坐标和值
    for (int i = 1; i <= n; i++)
    {
        int x, y, v;
        cin >> x >> y >> v;
        x++; y++;             // 坐标偏移(从1开始)
        a[x][y] += v;        // 累加值到对应位置
    }

    // 计算二维前缀和数组
    for (int i = 1; i <= 5001; i++)
    {
        for (int j = 1; j <= 5001; j++)
        {
            // 二维前缀和公式
            s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j];
        }
    }

    int res = 0;             // 存储最大和

    // 遍历所有可能的m×m正方形
    for (int i = m; i <= 5001; i++)
    {
        for (int j = m; j <= 5001; j++)
        {
            // 计算当前m×m正方形的和
            int current = s[i][j] - s[i-m][j] - s[i][j-m] + s[i-m][j-m];
            res = max(res, current);  // 更新最大值
        }
    }

    // 输出最大和
    cout << res << endl;

    return 0;
}
// 使用acwing模板二刷
#include <bits/stdc++.h>
using namespace std;

const int N = 10005;          // 定义二维数组最大尺寸
int n, m;                     // n: 点数, m: 正方形边长
int a[N][N];                  // 原始值数组
int s[N][N];                  // 二维前缀和数组

int main()
{
    // 输入点数和正方形边长
    cin >> n >> m;

    // 输入每个点的坐标和值
    for (int i = 1; i <= n; i++)
    {
        int x, y, v;
        cin >> x >> y >> v;
        x++; y++;             // 坐标偏移(从1开始)
        a[x][y] += v;         // 累加值到对应位置
    }

    // 计算二维前缀和数组
    for (int i = 1; i <= 5005; i++)
    {
        for (int j = 1; j <= 5005; j++)
        {
            // 二维前缀和公式
            s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
        }
    }

    int res = 0;              // 存储最大和

    // 遍历所有可能的m×m正方形
    for (int i = m; i <= 5005; i++)
    {
        for (int j = m; j <= 5005; j++)
        {
            // 计算当前m×m正方形的和
            int current = s[i][j] - s[i - m][j] - s[i][j - m] + s[i - m][j - m];
            res = max(res, current);  // 更新最大值
        }
    }

    // 输出最大和
    cout << res << endl;

    return 0;
}
#include <bits/stdc++.h>
using namespace std;

const int N = 10005;        // 定义最大坐标范围

int s[N][N];                // 二维前缀和数组
int a[N][N];                // 原始值数组(每个点的权重)
int max_x = 0, max_y = 0;   // 记录实际的最大x和y坐标
int n;                      // 目标点数量
int R;                      // 炸弹爆炸半径

int main()
{
    // 输入目标点数量和炸弹半径
    cin >> n >> R;

    // 限制半径大小,避免数组越界
    R = min(R, 5001);

    // 初始化最大坐标,确保至少覆盖R×R的范围
    max_x = R;
    max_y = R;

    // 输入每个目标点的坐标和权重
    for (int i = 0; i < n; i++)
    {
        int x, y, w;
        cin >> x >> y >> w;

        // 坐标从0开始,转换为从1开始(便于处理边界)
        x++;
        y++;

        // 在对应位置累加权值(可能有多个目标在同一位置)
        a[x][y] += w;

        // 更新最大坐标值
        max_x = max(x, max_x);
        max_y = max(y, max_y);
    }

    // 计算二维前缀和数组
    // s[i][j]表示从(1,1)到(i,j)的矩形区域和
    for (int i = 1; i <= max_x; i++)
    {
        for (int j = 1; j <= max_y; j++)
        {
            s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
        }
    }

    int ans = 0;  // 存储最大可摧毁价值

    // 枚举所有可能的炸弹放置位置
    // 炸弹覆盖区域为[i-R+1, i] × [j-R+1, j]的R×R正方形
    for (int i = R; i <= max_x; i++)
    {
        for (int j = R; j <= max_y; j++)
        {
            // 计算当前炸弹位置能摧毁的总价值
            // 使用前缀和快速计算R×R区域的和
            int current = s[i][j] - s[i - R][j] - s[i][j - R] + s[i - R][j - R];

            // 更新最大值
            ans = max(ans, current);
        }
    }

    // 输出最大可摧毁价值
    printf("%d\n", ans);

    return 0;
}

【运行结果】

2 1
0 0 1
1 1 1
1
posted @ 2026-09-02 21:40  团爸讲算法  阅读(11)  评论(0)    收藏  举报