【P2280 [HNOI2003]激光炸弹】二维前缀和完整解题
一、题意分析
1. 题目条件
- 平面上存在 $n$ 个目标,每个目标坐标 $(x,y)$,拥有价值 $v$,同一坐标可叠加多个目标价值;
- 激光炸弹爆炸范围是边长为 $m$、边平行坐标轴的正方形;
- 规则:落在正方形四条边上的目标不会被摧毁,只有正方形内部的目标才计入总价值;
- 目标:求投放一颗炸弹,能摧毁的目标总价值最大值。
2. 样例解析
输入:
2 1
0 0 1
1 1 1
正方形边长 $m=1$,只有严格处于正方形内部的点才会被炸毁。
我们把所有坐标 $+1$ 偏移,原 $(0,0)$ 变为 $(1,1)$;当枚举右下角 $(1,1)$ 时,对应查询边长为1的矩形,区域内仅包含点 $(1,1)$,总价值为1,也就是样例输出。
点 $(1,1)$ 原坐标是 $(1,1)$,会落在边长为1的正方形边界上,无法选取。因此全局最大总价值为1。
3. 模型转化
边长为 $m$、不含边界的正方形,等价于:右下角为 $(i,j)$、左上角为 $(i-m,j-m)$ 的开区间矩形。
坐标整体加1偏移后,我们只需要查询左上角 $(i-m+1,j-m+1)$、右下角 $(i,j)$ 的闭矩形,刚好对应所有严格在正方形内部的点。
问题转化为经典静态二维矩形求和,使用二维前缀和求解。

二、核心算法:二维前缀和
1. 二维前缀和定义
设 $s[i][j]$ 代表从左上角 $(1,1)$ 到右下角 $(i,j)$ 整个矩形内所有目标的总价值。
递推公式:
$$
s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + val(i,j)
$$
- $s[i-1][j]$:上方矩形总和;
- $s[i][j-1]$:左侧矩形总和;
- $s[i-1][j-1]$:左上角区域被重复计算,需要减去;
- $val(i,j)$:当前坐标点叠加的目标价值。
2. 固定边长矩形求和公式
对于右下角 $(i,j)$、边长为 $m$ 的矩形,左上角坐标为 $(i-m,\ j-m)$,区域总价值:
$$
sum = s[i][j] - s[i-m][j] - s[i][j-m] + s[i-m][j-m]
$$

三、完整解题步骤
步骤1:初始化与读入数据
- 定义二维数组
s存储坐标价值与前缀和,数组上限5005,覆盖坐标最大5000的范围; - 读取目标总数 $n$、正方形边长 $m$;
- 循环读取每个目标的坐标 $x,y$、价值 $v$:
- 执行
x++, y++坐标偏移,将原0开头坐标转为从1开始,规避前缀和下标0的边界判断; - 在
s[x][y]上累加价值,处理同一坐标多个目标的叠加情况。
- 执行
步骤2:预处理全局二维前缀和
双重循环遍历全部坐标范围(1~5001),套用二维前缀和递推公式,一次性预处理出所有矩形的总价值。
预处理完成后,任意矩形区域的总价值都可以在 $O(1)$ 时间内算出。
步骤3:枚举所有合法正方形,统计最大价值
- 枚举正方形右下角坐标 $i、j$,循环起点设置为 $m$,保证左上角坐标 $i-m、j-m$ 不会小于0,避免数组越界;
- 对每一组右下角,代入矩形求和公式,计算当前正方形能炸毁的目标总价值;
- 使用变量
res持续记录遍历过程中出现的最大总价值。
步骤4:输出结果
遍历全部合法正方形后,res存储全局最优解,打印输出。
四、代码分段讲解
1. 常量与全局数组
const int N = 5005;
int n, m; // n个目标 边长为m的正方形
int s[N][N]; // 价值二维前缀和数组
- $N=5005$:题目坐标最大值为5000,偏移后最大下标5001,数组预留安全空间;
s数组复用:先存储单点目标价值,预处理后存储前缀和。
2. 读入数据+坐标偏移
for(int i=0; i<n; i++){
int x, y, v;
scanf("%d%d%d",&x,&y,&v);
x++,y++; // 坐标+1,消除下标0
s[x][y]+=v;
}
x++,y++是关键优化,省去对 $i=0/j=0$ 的特殊边界判断;s[x][y] += v:多个目标在同一坐标时,价值直接累加。
3. 二维前缀和预处理
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];
}
}
严格套用二维前缀和递推公式,完成全图预处理。
4. 枚举所有正方形求最大值
int res = 0;
for(int i=m;i<=5001; i++){
for(int j=m; j<=5001; j++){
res = max(res, s[i][j]-s[i-m][j]-s[i][j-m]+s[i-m][j-m]);
}
}
- 循环从
m开始,保证左上角下标不越界; - 代入固定边长矩形求和公式,计算当前正方形总价值;
res = max(res, ...)不断更新全局最优答案。
5. 输出
printf("%d\n",res);
打印一颗炸弹能炸毁的最大总价值。
五、完整代码实现
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 5005;
int n, m;
int s[N][N];
int main() {
scanf("%d%d", &n, &m);
// 读入数据并坐标偏移
for(int i = 0; i < n; i++) {
int x, y, v;
scanf("%d%d%d", &x, &y, &v);
x++, y++; // 坐标+1,消除下标0
s[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];
}
}
// 枚举所有正方形求最大值
int res = 0;
for(int i = m; i <= 5001; i++) {
for(int j = m; j <= 5001; j++) {
int sum = s[i][j] - s[i-m][j] - s[i][j-m] + s[i-m][j-m];
res = max(res, sum);
}
}
printf("%d\n", res);
return 0;
}
六、复杂度分析
-
时间复杂度:$O(\max(5001^2, n))$
- 读入数据:$O(n)$
- 预处理前缀和:$O(5001^2)$
- 枚举正方形:$O((5001-m)^2)$
- 总体复杂度由预处理决定,约为 $2.5 \times 10^7$ 次操作,在1秒内可完成
-
空间复杂度:$O(5005^2)$,约为 $25 \times 10^6$ 个int,约100MB,在题目限制内
七、题型总结
-
适用场景:静态二维网格、无修改操作、大量矩形求和查询;
-
核心要点
- 坐标偏移:把0起点坐标转为1起点,简化前缀和边界逻辑;
- 几何转化:不含边界的正方形等价固定边长矩形查询;
-
模板通用性:二维前缀和是信奥基础模板,可拓展到任意大小矩形求和问题;
-
拓展区分:如果题目存在单点、区间修改操作,则不能使用前缀和,需改用二维树状数组/二维线段树。
八、常见错误与调试技巧
1. 数组越界
- 错误:循环边界处理不当,访问
s[-1][j]或s[i][-1] - 解决:确保所有下标从1开始,枚举时从
m开始
2. 坐标偏移遗漏
- 错误:忘记执行
x++, y++,导致前缀和计算错误 - 解决:在读入数据后立即偏移
3. 价值叠加处理
- 错误:同一坐标多个目标时使用赋值而非累加
- 解决:使用
+=而非=
4. 边界条件
- 当$m=0$时,需要特殊处理(但题目保证$m \geq 1$)
- 当$m > 5000$时,炸弹覆盖整个区域,答案等于所有目标价值总和
九、测试用例
测试1:样例输入
2 1
0 0 1
1 1 1
输出:1
测试2:边界测试
1 5000
0 0 100
输出:100
测试3:多目标叠加
3 2
1 1 10
1 1 20
2 2 30
输出:60((1,1)和(2,2)都在边长为2的正方形内)
测试4:大范围测试
100 10
// 随机生成100个目标
需要验证算法效率
十、学习建议
- 掌握前置知识:一维前缀和 → 二维前缀和 → 三维前缀和
- 练习题目:
- P1719 最大加权矩形
- P2004 领地选择
- P3406 海底高铁(一维前缀和应用)
- 拓展学习:
- 差分数组与前缀和的互逆关系
- 二维树状数组(支持单点修改)
- 二维线段树(支持区间修改查询)
总结:本题是二维前缀和的经典应用,关键在于理解坐标偏移的几何意义和前缀和的递推公式。掌握这个模板后,可以解决大量类似的静态矩形求和问题。
浙公网安备 33010602011771号