2.8C语言学习

本文介绍了二维前缀和的概念,其在计算二维数组中特定矩形区域数字和的应用,以及如何通过预处理和双指针技巧实现高效查询。文章详细讲解了计算方法和一个实际问题中的解决方案,涉及到了空间优化和边界调整。
摘要由CSDN通过智能技术生成

什么是二维前缀和
首先我们要明白前缀和是个什么东西:“前缀和”是在一维数组中(假设该数组为 ar[] ,以ar[1] 作为第一个ar数组的第一个元素)说的,那么第i个元素的前缀和sum[i] = ar[1 ] +ar[2] + …+ar[i] ; 二维前缀和就是在二维数组中用的,既然说是二维,那么一定是一个矩形有边界的平面内的数字之和,例如下图可以看成一个二维数组形成的一个平面,(1 ,1)为二维数组的第一个元素下标,(i,j)是数组内任意一个元素的下标,则由这两个下标组成的矩形区域(红色区域)内所有的数字之和,就是二维前缀和(这里强调一定是,数组的原点(1,1)必须是矩形的右上顶点,这样矩形区域内的数组之和才叫二维前缀和)


有啥用?
用处就是:用二维前缀和预处理过二维数组之后可以,可以以O(1)的时间求出在二维数组中的某个矩形(或正方形)的区域内数字之和(二维前缀和完美的体现了预处理的强大)。

具体实施步骤
1 .预处理二维数组(假设为map[][])
根据⬆️图:我们假设要求是(1,1)的(i,j)为定点到矩形区域到前缀和这里我们可以把前缀和单程面积来思考,
首先我们假设(1,1)到(i,j)组成的矩形区域面积(其实是前缀和)为S(i,j),
其次假设从(1,1)到(i - 1,j)区域的面积为S(i - 1 , j),
最后假设从(1,1)到(i , j - 1 )区域的面积为S(i , j - 1)。
另外,由于图中以(i - 1 ,j- 1)到(i, j)形成蓝色区域到面积就是map[ i][j].
这个时候我们通过拼凑的方法求出我想要的矩形区域的面积(其实是前缀和),
这样根据图我们很容易的推出:S(i , j) = S(i - 1 , j) + S(i , j - 1)- S( i - 1 , j - 1) +map[ i ][j]
在这个表达式中有两部分要注意:
第一部分: - S( i - 1 , j - 1)是因在在区域面积S(i - 1 , j) 与 S(i , j - 1) 重合的面积为 S( i - 1 , j - 1) (即图中的红色区域);
第二部分:+map[ i ][ j ] : 加上它是因为拼凑完面积之后还缺少图中蓝色区域所代表的面积,所以要加上 map[ i ][ j ] 所代表的蓝色区域的面积

2.实践求某个矩形/正方形区域内的数字和

1⃣️: 核心思想 : 还是拼凑出我们想求的区域的面积(这里的面积就是我们所求区域的数字和),通过之前的 预处理操作。
举例子来说吧:例如我们想求上图中绿色区域的数字和 S直接拼凑就行了:S = S(x2, y2)- S(x2,y1) - S(x1, y2) + S(x1 , y1);
:这里的+ S(x1 , y1)是因为我们重复减了S(x1 , y1 )这个区域两次,所以要再加上一次这个区域。

2⃣️:有了上面那个表达式后,还需要对这个式子略微做一些变形,一般题目给我们的条件就是:所求区域的竖直方向的宽度为b 、水平方向的长度为a,那么我们根据已知的x1、y1、a、b,可以推出我们所需要的的x2、y2.
公式如下:

x1 + a = x2 、y1 + b = y2
将这两个表达式带入表达式S得:S = S(x1 + a , y1 + b) - S(x1 + a , y1) - S(x1 , y1 + b) + S(x1 , y1);

3⃣️:我们还缺最后一步就可以搞定了:由于画图的原因我们发现边界好想不对,而且表达S所求的区域也有点不对,让们稍微的调整一下S表达式,让它求的恰好是我们所需要求的二维区域 并且 所求的矩阵区域和包括矩形区域的边上的元素,其实这个改动非常简单,就是在表达式中S中的x1、y1 分别替换成x1 - 1 、 y1 - 1,所以最终我们想求区域的数字和的表达式为S = S(x1 - 1 + a , y1 - 1 + b) - S(x1 - 1 + a , y1- 1) - S(x1 - 1 , y1 - 1 + b) + S(x1 - 1 , y1 - 1 );

P8783 [蓝桥杯 2022 省 B] 统计子矩阵

我们定义一个二维数组 a,代表输入的矩阵。

再定义一个二维数组 s,代表从 i=1,j=1 到目标点的前缀和。

最后用一个变量 ans 来存放最终输出的答案。

我们定义 i 为行,j 为列。

相信大家都可以轻易的写出以下代码:

for(int i = 1;i <= n;i ++){
    for(int j = 1;j <= m;j ++){
        scanf("%d",&a[i][j]);//输入矩阵 
        s[i][j] = a[i][j] + s[i][j - 1] + s[i - 1][j] - s[i - 1][j - 1];//计算前缀和 
    }
}

我们定义一个函数,传入子矩阵的左上角表示的点。由于前缀和公式的原因,我们直接传入的坐标为 (i−1,j−1)。

然后在函数中再~暴力枚举每一个点作为子矩阵的右下角:

void find(int x1,int y1){//左上角的点
    for(int x2 = x1 + 1;x2 <= n;x2 ++){
        for(int y2 = y1 + 1;y2 <= m;y2 ++){//右下角的点
            int sum = s[x2][y2] - s[x1][y2] - s[x2][y1] + s[x1][y1];
            if(sum <= k)ans++;
        }
    }
}

然后可以发现这样写有两个测试点 超时了

我们构造这样的循环:使矩阵中遍历的所谓左上角的点行和列都小于右下角的点,在不满足条件的同时做遍历,所以就会用到一个新的东西:双指针

双指针这个东西是不怎么学就可以理解的,说白了就是同时使用两个指针维护区间信息。由于这道题是二维的,确定两个点就用了 4 个指针。而用法详见双指针

浅讲述一下双指针这个本题重点:

  1. 定义 4 个变量,用来表示左上端点的坐标和右下端点的坐标。

  2. 遍历右下端点维护左上端点,符合此题描述。

  3. 简述第二条:确定一个左上端点,枚举所有右下端点。

  4. 在枚举完之后/过程中发现与条件不可能再符合的时候,重置(具体放哪看题意)。

一点也不抽象

意思就是定义 4 个指针,分别代表左上角和右下角,满足 i<j 且 l<r,遍历所有可能的情况,算入所有满足条件的情况。

这里代码的第二个大循环可以自行模拟以助理解。

注意细节:

按照题目的数据范围,sum 最大时是可能会超过 int 的范围的。

也就是说不开 long long 的话会wa几个测试点

最后是代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int n,m,k;
int a[510][510],b[510][510];
ll sum;
int main(){
	scanf("%d %d %d",&n,&m,&k);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%d",&a[i][j]);
			b[i][j]=a[i][j]+b[i-1][j]+b[i][j-1]-b[i-1][j-1];
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j++){
			for(int left=1,right=1;right<=m;right++){
				while(left<=right&&b[j][right]-b[i-1][right]-b[j][left-1]+b[i-1][left-1]>k)left++;
				sum+=right-left+1;
			}
		}
	}
	cout<<sum;
	return 0;
}

水一篇,提前预祝新春快乐

  • 9
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值