神机百炼1.12-二维前缀和

二维前缀和导图

食用指南:

对该算法程序编写以及踩坑点很熟悉的同学可以直接跳转到代码模板查看完整代码
只有基础算法的题目会有关于该算法的原理,实现步骤,代码注意点,代码模板,代码误区的讲解
非基础算法的题目只有题目分析,代码实现,代码误区
从这篇算法开始,精炼了算法内容,减少了冗余解释

题目描述:

  • 输入一个 n 行 m 列的整数矩阵,再输入 q 个询问,每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。

    对于每个询问输出子矩阵中所有数的和。

    输入格式
    第一行包含三个整数 n,m,q。
    接下来 n 行,每行包含 m 个整数,表示整数矩阵。
    接下来 q 行,每行包含四个整数 x1,y1,x2,y2,表示一组询问。

    输出格式
    共 q 行,每行输出一个询问的结果。

    数据范围
    1≤n,m≤1000,
    1≤q≤200000,
    1≤x1≤x2≤n,
    1≤y1≤y2≤m,
    −1000≤矩阵内元素的值≤1000
    输入样例:
    3 4 3
    1 7 2 4
    3 6 2 8
    2 1 2 3
    1 1 2 2
    2 1 3 4
    1 3 3 4
    输出样例:
    17
    27
    21

  • 题目来源:https://www.acwing.com/problem/content/798/

题目分析:

  • 矩形和:给定矩形左上顶点(x1,y1)和右下顶点(x2,y2),求该矩形内部所有点的和
  • 暴力:
    针对每一次询问,将目标矩形中的点相加
    最差时间复杂度O(q n m),当每次询问都求n行m列的矩形内部和时,每次都双层循环求和
    数据量:n==m==1000,q==200000
    针对最差时间复杂度需要400亿ms,严重超时
  • 二维前缀和:
    核心思想还是打表后查表
    遍历矩形,建立以(1,1)为左上顶点,(i,j)为右下顶点的矩形,s[i][j]记录该矩形内点之和
    查表时,由一个大矩形和一个小矩形可以得出目标矩形的内点之和

算法原理:

含义:

  • s[i][j]表示以s[1][1]为左上端点,s[i][j]为右下端点的矩形区域内二维数组arr[][]元素的和
    对,就是你想的那样,以(1,1)为起点还是为了好求s[i][j]
  • 双层循环求s[i][j]:
    共计nm个点,每个的复杂度都是nm,最后打表就花费O(n2m2),显然不可取,需要类似一维前缀和的前缀和递推公式
  • 递归公式求s[i][j]:
    共计nm个点,每个的复杂度都是O(1),打表复杂度O(nm)
    打表公式:s[i][j] = s[i-1][j]+s[i][j-1]-s[i-1][j-1]+arr[i][j];
    大矩形 = 中矩形1 + 中矩形2 - 小矩形3 + (i,j)点
    打表公式

作用:

  • 一次打表,多次查表:
    打表耗时O(nm),因为本身有nm个点,而计算s[n][m]本身花费O(1),因为有打表公式
    查表耗时O(1),因为有二维前缀和查表公式
  • 求固定起点矩形和的打表公式:
    s[i][j] = s[i-1][j]+s[i][j-1]-s[i-1][j-1]+arr[i][j];
  • 求任意矩形和的查表公式:
    int tmp = s[x2][y2] - x[x1-1][y2] - x[x2][y1-1] + x[x1-1][y1-1];
    图解:绿色斜线部分 = 大矩形 - 黄色横线部分 - 红色竖线部分 + 黄色红色交叉部分
    查表公式

存储形式:

  • 二维数组即可,同样为了二维前缀和便于求取而从arr[1][1]开始存储,arr[0][x]和arr[y][0]都不存数据
  • 这次就必须先输入完成后,再建立二维前缀和数组了

写作步骤:

打表:

  • 输入完全后进行打表,建立以(1,1)为左上顶点,(i,j)为右下顶点的矩形的二维前缀和
  • 二维前缀和打表公式:s[i][j] = s[i-1][j]+s[i][j-1]-s[i-1][j-1]+arr[i][j];

查表:

  • 二维前缀和查表公式:int tmp = s[x2][y2] - x[x1-1][y2] - x[x2][y1-1] + x[x1-1][y1-1];

代码模板:

//以后不写经典头文件 和 int main()了
int N = 10010;
int s[N][N];
int arr[N][N];
int main(){
    int n=0, m=0, q=0;
    cin >> n >>m >>q;
	for(int i=1; i<=n; i++){
    	for(int j=1; j<=m; j++){
			cin >>arr[i][j];
        }
	}
	//打表公式:计算本身耗时O(1)
    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]+arr[i][j];
        }
    }
    for(int i=0; i<q; i++){
		int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
		cin >>x1 >>y1 >>x2 >>y2;
		//查表公式:O(1)
		int tmp = s[x2][y2] - x[x1-1][y2] - x[x2][y1-1] + x[x1-1][y1-1];
		cout<<tmp<<endl;
	}
}

代码误区:

1. 为什么打表时间复杂度是O(nm)?

  • 每个点与(1,1)构成的矩形内点和的计算有递推公式:
    s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + arr[i][j]l
  • 一共有n*m个点

2. 二维前缀和一共几个公式?

  1. 打表公式:s[i][j] = s[i-1][j]+s[i][j-1]-s[i-1][j-1]+arr[i][j];
  2. 查表公式:int tmp = s[x2][y2] - x[x1-1][y2] - x[x2][y1-1] + x[x1-1][y1-1];

3. 致命问题-公式背不准,该-1处没有-1:

  • 死记公式很容易出错,应该结合我画的两个示意图来有节奏的写
  • 打表拼凑公式:
    (i,j)矩形由小矩形拼凑而来:(i-1,j)矩形,(i,j-1)矩形,(i-1,-1)矩形,(i,j)点
  • 查表抠图公式:
    (x1,y1)-(x2,y2)矩形由大矩形减小矩形而来:(x2,y2)矩形-(x1-1,y2)矩形-(x2,y1-1)矩形+(x1-1,y1-1)矩形
  • 查表矩形发生了两点(x1,y1)和(x2,y2)的混合搭配;打表公式仅仅有1个点,不存在混合搭配

本篇感想:

  • 这个打表公式和查表公式确实难记嗷,所以我决定再写一遍,你呢?
  • 打表公式:s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + arr[i][j];
  • 查表公式:int tmp = s[x2][y2] - s[x1-1][y2] - s[x2][y1-1]+s[x1-1][y1-1];
  • 看完本篇博客,恭喜已登《练气境-初期》
    练气期初阶
    距离登仙境不远了,加油 登仙境初期
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

starnight531

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值