为什么我为OI泪目,因为我菜得离谱......
二维前缀和
引子
二维前缀和,仅仅是由一维前缀和进阶了一维而已。
为了方便后面的学习,我先给出二维前缀和重点代码。
处理二维前缀和
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
查询二维前缀和
int a1,b1,a2,b2;//待查询的点A(a1,b1)左上角 B(a2,b2)右下角
int ans=sum[a2][b2]-sum[a2][b1-1]-sum[a1-1][b2]+sum[a1-1][b1-1];//ans:答案
接下来以例题举例。
例题
最大加权矩形
题目描述
为了更好的备战 NOIP2013,电脑组的几个女孩子 LYQ,ZSC,ZHQ 认为,我们不光需要机房,我们还需要运动,于是就决定找校长申请一块电脑组的课余运动场地,听说她们都是电脑组的高手,校长没有马上答应他们,而是先给她们出了一道数学题,并且告诉她们:你们能获得的运动场地的面积就是你们能找到的这个最大的数字。
校长先给他们一个 \(n\times n\) 矩阵。要求矩阵中最大加权矩形,即矩阵的每一个元素都有一权值,权值定义在整数集上。从中找一矩形,矩形大小无限制,是其中包含的所有元素的和最大 。矩阵的每个元素属于 \([-127,127]\) ,例如
0 –2 –7 0
9 2 –6 2
-4 1 –4 1
-1 8 0 –2
在左下角:
9 2
-4 1
-1 8
和为 \(15\)。
几个女孩子有点犯难了,于是就找到了电脑组精打细算的 HZH,TZY 小朋友帮忙计算,但是遗憾的是他们的答案都不一样,涉及土地的事情我们可不能含糊,你能帮忙计算出校长所给的矩形中加权和最大的矩形吗?
输入格式
第一行:\(n\),接下来是 \(n\) 行 \(n\) 列的矩阵。
输出格式
最大矩形(子矩阵)的和。
样例 #1
样例输入 #1
4
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
样例输出 #1
15
提示
\(1 \leq n\le 120\)
分析
看一下数据,\(n<=120\),乍看挺小,但直接暴力是\(O(n^6)\),直接爆掉。
所以要另外寻找做法。
Step1
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
我们令sum[i][j]:从(1,1)到(i,j)的矩阵和,我们就能发现蓝色部分
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
为红色部分
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
加上绿色部分
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
减去紫色部分
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
最后再加上\(a_i,_j\)这个点
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
所以Get代码
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
sum[i-1][j],sum[i][j-1],sum[i-1][j-1],a[i][j]都是已知的,所以不用担心。
Step2
再看查询
比如我们要查询(2,2)~(3,3)这个矩阵内的和
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
其实就相当于
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
减去
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
再减去
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
最后根据容斥原理,在紫色点上连减两次,需要补回这个矩阵
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
所以GET代码(这里要枚举左上点和右下点坐标)
for(int x_1=1;x_1<=n;x_1++)
for(int y_1=1;y_1<=n;y_1++)
for(int x_2=x_1;x_2<=n;x_2++)
for(int y_2=y_1;y_2<=n;y_2++)
lans=max(lans,sum[x_2][y_2]-sum[x_1-1][y_2]-sum[x_2][y_1-1]+sum[x_1-1][y_1-1]);
提示:珍爱生命,远离y0,y1.
CODE
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int tsn=120+5;
int n;
int lans=INT_MIN;
int a[tsn][tsn];
int qz[tsn][tsn];
int sum[tsn][tsn];
signed main()
{
#ifndef ONLINE_JUDGE
freopen("00in.txt","r",stdin);
freopen("00out.txt","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
cin>>a[i][j];
qz[i][j]=qz[i][j-1]+a[i][j];
sum[i][j]=qz[i][j]+sum[i-1][j];
}
for(int x_1=1;x_1<=n;x_1++)
for(int y_1=1;y_1<=n;y_1++)
for(int x_2=x_1;x_2<=n;x_2++)
for(int y_2=y_1;y_2<=n;y_2++)
lans=max(lans,sum[x_2][y_2]-sum[x_1-1][y_2]-sum[x_2][y_1-1]+sum[x_1-1][y_1-1]);
cout<<lans;
return 0;
}
[HNOI2003] 激光炸弹
题目描述
一种新型的激光炸弹,可以摧毁一个边长为 \(m\) 的正方形内的所有目标。现在地图上有 \(n\) 个目标,用整数 \(x_i\) , \(y_i\) 表示目标在地图上的位置,每个目标都有一个价值 \(v_i\)。激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆破范围,即那个边长为 \(m\) 的边必须与 \(x\) 轴,\(y\) 轴平行。若目标位于爆破正方形的边上,该目标不会被摧毁。
现在你的任务是计算一颗炸弹最多能炸掉地图上总价值为多少的目标。
可能存在多个目标在同一位置上的情况。
输入格式
输入的第一行为整数 \(n\) 和整数 \(m\);
接下来的 \(n\) 行,每行有 \(3\) 个整数 \(x, y, v\),表示一个目标的坐标与价值。
输出格式
输出仅有一个正整数,表示一颗炸弹最多能炸掉地图上总价值为多少的目标(结果不会超过 \(32767\) )。
样例 #1
样例输入 #1
2 1
0 0 1
1 1 1
样例输出 #1
1
提示
数据规模与约定
- 对于 \(100\%\) 的数据,保证 \(1 \le n \le 10^4\),\(0 \le x_i ,y_i \le 5\times 10^3\),\(1 \le m \le 5\times 10^3\),\(1 \le v_i < 100\)。
分析
CODE (only 91分,死调不过,仅供参考)
#include<bits/stdc++.h>
using namespace std;
const short tsn=5e3+5;
short n,m;
short maxx=0,maxy=0;
short lans=-1314;
short a[tsn][tsn];
short qz[tsn][tsn];
int sum[tsn][tsn];
signed main()
{
#ifndef ONLINE_JUDGE
freopen("00in.txt","r",stdin);
freopen("00out.txt","w",stdout);
#endif
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;//what can I say?
for(short i=1,x,y,v;i<=n;i++)
{
cin>>x>>y>>v;x++,y++;
a[x][y]+=v;
maxx=max(int(x),int(maxx)),maxy=max(int(y),int(maxy));
}
for(short i=1;i<=maxx;i++)
for(short j=1;j<=maxy;j++)
{
qz[i][j]=qz[i][j-1]+a[i][j];
sum[i][j]=sum[i-1][j]+qz[i][j];
}
for(short a1=m;a1<=maxx;a1++)
for(short b1=m;b1<=maxy;b1++)
{
lans=max(int(lans),int(sum[a1][b1]-sum[a1-m][b1]-sum[a1][b1-m]+sum[a1-m][b1-m]));
}
cout<<lans;
return 0;
}
完结撒花
图片来自网络。