洛谷P2280[HNOI2003] 激光炸弹(二维前缀和)
题目描述
一种新型的激光炸弹,可以摧毁一个边长为 m 的正方形内的所有目标。现在地图上有 n 个目标,用整数 xi , yi 表示目标在地图上的位置,每个目标都有一个价值vi .激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆破范围,即那个边长为 m 的边必须与 x 轴, y 轴平行。若目标位于爆破正方形的边上,该目标不会被摧毁。
现在你的任务是计算一颗炸弹最多能炸掉地图上总价值为多少的目标。
输入格式
输入的第一行为整数 n 和整数 m,
接下来的 n 行,每行有 3 个整数 x, y, v,表示一个目标的坐标与价值。
输出格式
输出仅有一个正整数,表示一颗炸弹最多能炸掉地图上总价值为多少的目标(结果不会超过 32767)。
输入输出样例
2 1 0 0 1 1 1 1
1
说明/提示
数据规模与约定
1000
简单来说,这个题是一个二维前缀和的题目
第一种思路是这样的:
1. 把所有有值的点按x值为第一关键字,y值为第二关键字排序。
2. 枚举所有可能的y值范围(最低为[0 r-1],最高为[5000-r, 4999]),对于每个y值范围,从上往下扫一遍所有有值的点,统计每个x值对应的点中y值落在当前枚举的y值的区间内的值的和,并计算这个和的前缀和,然后枚举所有可能的x值范围(最低为[0 r-1],最高为[5000-r, 4999]),根据前缀和计算出对应的子矩阵和
在所有可能的x值范围和y值范围中,找到的最大的子矩阵和就是答案
(感觉写的好拗口,好难懂,看代码吧hhh)
时间复杂度N*logN + (5000-r) * (N + 5000 + 5000-r)
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> using namespace std; const int N = 10005; struct point { int x; int y; int w; bool operator <(const point &t) const { if(x != t.x) return x < t.x; else return y < t.y; } }; point arr[N]; int main() { int n, r; cin >> n >> r; for(int i = 0; i < n; i++) { scanf("%d%d%d", &arr[i].x, &arr[i].y, &arr[i].w); } sort(arr, arr+n); int maxResult = -1000000; for(int i = 0; i <= 5000- r; i++) { //纵坐标 int minValue = i; int maxValue = i + r - 1; int lastX = -1; bool isFirst = true; int totalWeight = 0; int weight[N]; memset(weight, 0, sizeof(weight)); for(int j = 0; j < n; j++) { //遍历所有点 if(!(arr[j].y >= minValue && arr[j].y <= maxValue)) continue; if(isFirst) { isFirst = false; lastX = arr[j].x; totalWeight = arr[j].w; continue; } if(arr[j].x != lastX) { //结算 weight[lastX] = totalWeight; lastX = arr[j].x; } totalWeight += arr[j].w; } if(totalWeight != 0) weight[lastX] = totalWeight; //完全完成前缀和的计算 weight[0] = weight[0]!=0 ? weight[0] : 0; for(int j = 1; j < 5000; j++) { if(weight[j] == 0) weight[j] = weight[j-1]; } for(int j = 0; j <= 5000 - r; j++) { if(j == 0) maxResult = max(maxResult, weight[r-1]); else maxResult = max(maxResult, weight[r-1+j] - weight[j-1]); } } cout << maxResult << endl; return 0; }
第二种思路是标准的二维前缀和的思路(也是非常简单的思路):
1. 计算二维前缀和
2. 枚举所有子矩阵(我的程序中是枚举的矩阵的最左上角位置),计算子矩阵的和,然后找到一个最大的maxValue输出就可以了
这里我的下标是与输入的一致的(最小的下标是0),所以在计算前缀和的时候需要加入很多特判(不然-1的时候会向下越界)
时间复杂度是5000*5000+(5000-r)*(5000-r) => O(5000*5000)
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N = 5005; int graph[N][N]; int main() { int n, r; cin >> n >> r; for(int i = 0; i < n; i++) { int x, y; scanf("%d%d", &x, &y); scanf("%d", &graph[x][y]); } //开始计算前缀和 int sum = 0; for(int j = 0; j < 5000; j++) { //计算第一行的前缀和 sum += graph[0][j]; graph[0][j] = sum; } //计算其余行的前缀和 for(int i = 1; i < 5000; i++) { sum = 0; for(int j = 0; j < 5000; j++) { sum += graph[i][j]; graph[i][j] = sum + graph[i-1][j]; } } int maxValue = -1000; for(int i = 0; i <= 5000 - r; i++) { for(int j = 0; j <= 5000 - r; j++) { //枚举的是左上角的位置 int temp = graph[i+r-1][j+r-1]; if(i != 0) temp -= graph[i-1][j+r-1]; if(j != 0) temp -= graph[i+r-1][j-1]; if(i != 0 && j != 0) temp += graph[i-1][j-1]; maxValue = max(maxValue, temp); } } cout << maxValue << endl; return 0; }
整理一下思路,把下标整体加1,会使得程序好写很多:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N = 5005; int arr[N][N]; int main () { int n, r; cin >> n >> r; int x, y; for(int i = 0; i < n; i++) { scanf("%d%d",&x, &y); scanf("%d", &arr[x+1][y+1]); } //计算前缀和 for(int i = 1; i <= 5000; i++) { int sum = 0; for(int j = 1; j <= 5000; j++) { sum += arr[i][j]; arr[i][j] = sum + arr[i-1][j]; } } int maxValue = -1; //枚举左上角顶点 for(int i = 1; i <= 5001-r; i++) { for(int j = 1; j <= 5001-r; j++) { int temp = arr[i+r-1][j+r-1] + arr[i-1][j-1] - arr[i-1][j+r-1] - arr[i+r-1][j-1]; maxValue = max(maxValue, temp); } } cout << maxValue << endl; return 0; }