激光炸弹
题目描述
题意解释
给定一个最多 5000 × 5000 5000\times 5000 5000×5000的地图,给出地图上一些目标点(这些点都有价值val),给你一个正方形去框选地图上的目标点,并问所能框选到的目标点之和的最大值是多少。
思路分析
设 s [ i ] [ j ] s[i][j] s[i][j]表示地图上从坐标 ( 1 , 1 ) (1,1) (1,1)到坐标 ( i , j ) (i,j) (i,j)的所有目标点的价值之和。
问题:如何求出一块特定的正方形内的所有目标点的价值之和呢?
例如上图,假设我们想要求出蓝色正方形内的所有目标点的价值之和,那么只需要用大正方形-两个红色的长方形+绿色正方形。R是要枚举的正方形的边长。
-
大正方形: s [ i ] [ j ] s[i][j] s[i][j]
-
上面的红色长方形: s [ i − R ] [ j ] s[i-R][j] s[i−R][j]
-
左边的红色长方形: s [ i , j − R ] s[i,j-R] s[i,j−R]
-
蓝色的小正方形: s [ i ] [ j ] − s [ i − R ] [ j ] − s [ i ] [ j − R ] + s [ i − R ] [ j − R ] s[i][j]-s[i-R][j]-s[i][j-R]+s[i-R][j-R] s[i][j]−s[i−R][j]−s[i][j−R]+s[i−R][j−R]
细节
问题1:半径R的范围?
因为题目给定的地图最大是 5000 × 5000 5000\times 5000 5000×5000,所以R的最大范围应该也是5000.但是因为我们求前缀和数组时,R是从1到5001,即范围是5000.因此,如果我们输入的R是 ≥ 5001 \geq 5001 ≥5001,那么就应该让 R = m i n ( R , 5001 ) R=min(R,5001) R=min(R,5001),因为如果R的范围超过了5000,那么这个炸弹的爆破范围一定是已经全部包括了 5000 × 5000 5000\times 5000 5000×5000的地图了。
问题2:为什么是 s [ x ] [ y ] + = w s[x][y]+=w s[x][y]+=w?
因为同一个坐标的的位置上,可能会有多个目标点,所以要对这个位置累加其上的所有目标点的价值。因此,为了防止同一个位置上有多个目标点,要写成 s [ x ] [ y ] + = w s[x][y]+=w s[x][y]+=w,而不能写成 s [ x ] [ y ] = w s[x][y]=w s[x][y]=w。
问题3:为什么是 s [ i ] [ j ] + = s [ i − 1 ] [ j ] + s [ i ] [ j − 1 ] − s [ i − 1 ] [ j − 1 ] s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1] s[i][j]+=s[i−1][j]+s[i][j−1]−s[i−1][j−1] ?
首先,在求矩阵的前缀和时,我们一般都是用两个二维数组, a [ i ] [ j ] a[i][j] a[i][j]表示某个数值, s [ i ] [ j ] s[i][j] s[i][j]表示以坐标 ( i , j ) (i,j) (i,j)为右下角结尾的子矩阵里面全部数值的总和。然后我们一般是写成 s [ i ] [ j ] = s [ i − 1 ] [ j ] + s [ i ] [ j − 1 ] − s [ i − 1 ] [ j − 1 ] + a [ i ] [ j ] s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j] s[i][j]=s[i−1][j]+s[i][j−1]−s[i−1][j−1]+a[i][j]。但是因为这道题内存卡得死,不能开两个数组。因此,只能开一个前缀和数组 s [ i ] [ j ] s[i][j] s[i][j]。然后,写成 s [ i ] [ j ] + = s [ i − 1 ] [ j ] + s [ i ] [ j − 1 ] + s [ i − 1 ] [ j ] − s [ i − 1 ] [ j − 1 ] s[i][j]+=s[i-1][j]+s[i][j-1]+s[i-1][j]-s[i-1][j-1] s[i][j]+=s[i−1][j]+s[i][j−1]+s[i−1][j]−s[i−1][j−1]可以理解成 s [ i ] [ j ] = s [ i − 1 ] [ j ] + s [ i ] [ j − 1 ] − s [ i − 1 ] [ j − 1 ] + s [ i ] [ j ] s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+s[i][j] s[i][j]=s[i−1][j]+s[i][j−1]−s[i−1][j−1]+s[i][j]。因为 s [ N ] [ N ] s[N][N] s[N][N]前缀和数组是全局变量,所以初始值为0,可以把等式右边的 s [ i ] [ j ] s[i][j] s[i][j]看做是 a [ i ] [ j ] a[i][j] a[i][j]。
其实,更合理的解释,就是同一个坐标的位置,可能会有多个目标点,所以求对这个位置求前缀和时,要累加其上的所有目标点的价值。
问题4:如何理解 r e s = m a x ( r e s , s [ i ] [ j ] − s [ i − R ] [ j ] − s [ i ] [ j − R ] + s [ i − R ] [ j − R ] ) res=max(res,s[i][j]-s[i-R][j]-s[i][j-R]+s[i-R][j-R]) res=max(res,s[i][j]−s[i−R][j]−s[i][j−R]+s[i−R][j−R]) ?
因为我们是对R从小到大枚举到5001,对于枚举到的R,看看这个 R × R R\times R R×R的正方形内所有目标点的价值总和,因为要枚举很多R,所有会有很多正方形,然后对这些枚举到的正方形,求 m a x max max,即可得到某个确定的 R × R R\times R R×R的正方形内所有目标点的价值总和,那么此时就是最大值。
代码
#include<iostream> #include<algorithm> using namespace std; const int N=5010; int s[N][N]; //前缀和数组 int main() { int n,R; //输入n个目标和边长R scanf("%d%d",&n,&R); R=min(R,5001); //对R取最小值 //输入n个目标点的信息 for(int i=0;i<n;i++) { int x,y,w; //坐标(x,y)、价值 scanf("%d%d%d",&x,&y,&w); x++,y++; //因为前缀和一般从1开始取,而题目输入的x和y是从0开始。所以要+1,方便处理前缀和数组 s[x][y]+=w; //一个位置上可能有多个目标,所以对这个位置累加其价值 } //预处理前缀和 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; //代表一颗炸弹最多能炸掉地图上目标的总价值数目。 //枚举边长R,确定选择哪个R*R的正方形,可以使得总价值最大 for(int i=R;i<=5001;i++) { for(int j=R;j<=5001;j++) { res=max(res,s[i][j]-s[i-R][j]-s[i][j-R]+s[i-R][j-R]); } } printf("%d\n",res); return 0; }