import java.io.*;
/*
* 题目分析:一个最大5000 * 5000 的矩阵, 爆炸范围在 [0,10e9]
* 地图上的目标是随机分布,如果要暴力计算每一个区间R的权值,会很麻烦
* 可以用二维前缀和先将权值存起来
* for(int i = 1;i <= n;i ++) {
for(int j = 1;j <= m;j ++) {
g[i][j] = g[i][j-1] + g[i-1][j] - g[i-1][j-1] + read[j-1];
}
}
//前缀和下标有出现-1,所以下标从1开始算。真正的有效下标应该是[1,5001]
再遍历其中的每个区间R * R取一个最大值
其中(n,m) 表示距离远点最远的那个点(只遍历有目标的位置)
for(int i = R;i <= n; i++)
for(int j = R;j <= m; j++)
res = Math.max(res,s[i][j] - s[i - R + 1 - 1][j] - s[i][j-R+1-1] + s[i-R+1-1][j-R+1-1]);
所以读入位置的时候,除了要更新该位置的权值w,还要不断更新n,m.用来后序遍历
当 R < 5001,可以用上面的式子算,如果 R >= 5001,就把R设置成 5001,否则进不去循环
如果把R设置成5001,一般n,m也到不了那么大,所以初始化n = m = R (防止出现爆炸范围大于所以目标位置)
半径为R的爆炸范围可以摧毁 (R + 1)(R + 1)个点 但因为爆炸范围要与边平行,所以少炸一个
因为只有范围 R * R能炸到,所以爆炸点上下、左右的跨度只有 R - 1;
在算前缀和的时候, x1 = x2 - (R - 1) - 1 == x2 - R
*/
public class Main {
static int n,m; //其中(n,m) 表示距离远点最远的那个点
static final int N = 5010;
static int[][] s = new int[N][N]; //用于存放前缀和
//输入较多,用BufferedReader
static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
public static void main(String[] args) throws IOException {
int cnt,R; //表示目标个数和爆炸范围
String[] init = in.readLine().split(" ");
cnt = Integer.parseInt(init[0]);
//R最多取到地图边界
R = Math.min(5001, Integer.parseInt(init[1]));
//因为要从R ~ n,与R ~ m遍历所有目标,所以如果 n < R 或者 m < R 会遍历不到
//所以先让n = m = R
n = m = R;
while(cnt -- > 0) {
//init可以复用
init = in.readLine().split(" ");
int x = Integer.parseInt(init[0]);
int y = Integer.parseInt(init[1]);
int w = Integer.parseInt(init[2]);
//计算前缀和下标从1开始,题目是从0开始,所有把题目的所有下标都++
x++;
y++;
n = Math.max(n,x);
m = Math.max(m,y);
s[x][y] += w;
}
for(int i = 1;i <=n; i ++) {
for(int j = 1;j <= m; j ++) {
s[i][j] += s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];
}
}
//遍历半径为R的正方形
int res = 0;
for(int i = R;i <= n;i ++) {
for(int j = R;j <= m;j ++) {
res = Math.max(res, s[i][j] - s[i - R][j] - s[i][j - R] + s[i - R][j - R]);
}
}
System.out.println(res);
in.close();
}
}
前缀和例题:一维版前缀和
二维前缀和