AcWing 4405. 统计子矩阵(每日一题)

如果你觉得这篇题解对你有用,可以点点关注再走呗~

题目描述

给定一个 N×M 的矩阵 A,请你统计有多少个子矩阵 (最小 1×1,最大 N×M) 满足子矩阵中所有数的和不超过给定的整数 K ?
输入格式

第一行包含三个整数 N,M 和 K。

之后 N 行每行包含 M 个整数,代表矩阵 A。
输出格式

一个整数代表答案。

数据范围

对于 30%
的数据,N,M≤20,
对于 70% 的数据,N,M≤100,
对于 100% 的数据,1≤N,M≤500;0≤Aij≤1000;1≤K≤2.5×108。
输入样例:

3 4 10
1 2 3 4
5 6 7 8
9 10 11 12

输出样例:

19

样例解释

满足条件的子矩阵一共有 19,包含:大小为 1×1的有 10个。
大小为 1×2的有 3个。
大小为 1×3的有 2个。
大小为 1×4的有 1个。
大小为 2×1的有 3 个。

分析

直接暴力枚举
需要枚举上、下、左、右 四个边界
时间复杂度 O(n^4) = O(10^10)
n=500 500^4=62500000000=6.25x10^10
定TLE
我们看能不能优化成三维去做?
500^3=125000000=1.25*10^8
这样看还是过不了
于是引入双指针算法枚举次数降为1.25*10^8/2
时间复杂度为**O(6*10^7)**
这样便可以过了

优化

双指针算法+前缀和
题目问满足子矩阵中所有数的和不超过给定的整数 K的子矩阵
所以我们用前缀和去处理矩阵内所有数的和
时间复杂度为**O(1)**

双指针怎么实现?

下面让我带你来分析
我们需要用到4个变量:
up(矩阵的上界)
down(矩阵的下界)
l(矩阵的左界)
r(矩阵的右界)

在这里插入图片描述

先固定上下界,现在上下界固定下来了。

在这里插入图片描述

再依次去枚举上下界,每次枚举上下界移动左右边界

在这里插入图片描述

我们需要统计的是左右边界移动的矩阵内有多少列

有多少列就有多少个满足条件的矩阵

矩阵个数r-l+1
为什么统计的是**矩阵内的列数****见盲点分析

怎么样去移动左右边界?
在这里插入图片描述

进一步--------------
在这里插入图片描述

先固定右边界,再去枚举左边界

过程分析

左边界l满足当左边界 l 到右边界 r 这一矩阵内所有数的和> K 时。
说明我们的 l~r 这块矩阵不能包含太多的数,l 需要往右移动。
l 一直移动下去,直至 l 移动到 l~r 这块矩阵的所有数的和值<= k 时,l停止

在计算完这块矩阵内列的矩阵个数后,再去移动r,依次类推。
确保每次 l~r 这块矩阵内的所有数的和均<=k
这样便可以将矩阵的个数不重不漏的计算出来。
在这里插入图片描述

注意

怕很多同学不清楚每一列(个)矩阵代表一列(个)数字
l、r也是一列矩阵,只不过为了便于理解,将l、r抽象成两条线/边界来看
其实l、r本身是一列,是有数字的!!!
下面的分析与此表述同步!
数字图如下:
在这里插入图片描述

盲点分析

为什么去枚举l~r列数
我们在枚举时枚举的并不全是向前面的图形那样
上面的图形描绘的是整体的矩阵效果,便于理解双指针的移动。

下面我们来看看枚举时的过程和细节
在这里插入图片描述

枚举的过程可以抽象看成是在一整个矩阵内拖动橙色长方形从右上往左下拉
实际上是由很多的l、r不断分割矩阵的数,见下图:
在这里插入图片描述

注:图形是便于理解,每一条线不代表一种情况,实际计算时不会重复累加。

从图中便可以看到,我们将图形理解成动态的过程,从左上到右下不断划分出小矩阵,再去判断是否满足条件,从而将矩阵的总数加起来。
像图中的蓝色小矩阵即是枚举是每一列的矩阵个数,依次移动指针,累加矩阵个数。

注意:l、r也是一列矩阵,是占据了数字的一列。

为什么计算的是列数?

在这里插入图片描述

枚举时计算的是每一列的矩阵数,这是因为我们是从右上往左下移动。
如果统计的是每一行的矩阵数则同一行的矩阵数都被并入最终的结果其中,造成结果的错漏,而从每一行去统计矩阵数也与我们枚举时移动的方向不一致,因此不能是统计每一行的矩阵数。同学们仔细想一下对不对?

关于r-l+1的由来:

先看最普遍的情况:

在这里插入图片描述

由上图:l在左边,r在右边,中间间隔3列,即3个矩阵,再加上两列其本身所在的那一列矩阵。总共是3+2=5个矩阵,代入公式检验一下,检查一下对不对?
根据图形,假设r=5,l=1,中间的列数分别为2、3、4总共3
r-l+1=5-1+1=5 结果正确!

再看边界情况:

r=l时,代入公式r-l+1=0+1=1。
得出1个,而这个便是r、l所在这一列的矩阵,见下图:
在这里插入图片描述

注:每一个矩阵代表一个数字

Accode

import java.util.*;
public class Main{
	static int N=510;
	static int [][]a=new int[N][N];
	static int [][]s=new int[N][N];
	static int n,m,k;
	public static void main(String []args) {
	Scanner sc=new Scanner(System.in);
	n=sc.nextInt();
	m=sc.nextInt();
	k=sc.nextInt();
	//预处理前缀和,便于计算矩阵内所有数的和值
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			a[i][j]=sc.nextInt();
			s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
					}
	}
	
	long res=0;
	//枚举上下边界
	for(int up=1;up<=n;up++) {
		for(int down=up;down<=n;down++) {
		    
			int l=1;//左边界
			int r=1;//右边界
			
			while(r<=m) {
			    //右边界在整个矩阵内
			    
				while(l<=r&&!check(up,l,down,r))l++;
				//不满足和值小于等于k则移动左边界
				
				res+=(long)r-l+1;
				//统计列数
				r++;
				//左边界固定下来,可以移动右边界
			}
			
		}
	}
	System.out.println(res);	
	}
	static boolean check(int x1,int y1,int x2,int y2) {
		//前缀和
		long sum=s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1];
		//判断是否满足sum小于等于k这一条件
		return sum<=k;
	}
}

往期回顾

不清楚蓝桥杯考什么的点点下方👇

考点秘籍

想背纯享模版的伙伴们点点下方👇

蓝桥杯省一你一定不能错过的模板大全(第一期)

蓝桥杯省一你一定不能错过的模板大全(第二期)

蓝桥杯省一你一定不能错过的模板大全(第三期)

蓝桥杯省一你一定不能错过的模板大全(第四期)!!!

想背注释模版的伙伴们点点下方👇

蓝桥杯必背第一期

蓝桥杯必背第二期

往期精彩回顾

蓝桥杯上岸每日N题 第一期(一)!!!

蓝桥杯上岸每日N题第一期(二)!!!

蓝桥杯上岸每日N题第一期(三)!!!

蓝桥杯上岸每日N题第二期(一)!!!

蓝桥杯上岸每日N题第三期(一)!!!

蓝桥杯上岸每日N题 第四期(最少刷题数)!!!

蓝桥杯上岸每日N题 第五期(山)!!!

蓝桥杯上岸每日N题 第六期(求阶乘)!!!

蓝桥杯上岸每日N题 第七期(小猫爬山)!!!

蓝桥杯上岸每日N题 第八期 (全球变暖)!!!

蓝桥杯每日N题 (消灭老鼠)

蓝桥杯每日N题(杨辉三角形)

蓝桥杯每日N题 (砝码称重)

蓝桥杯上岸每日N题(鸡尾酒)

操作系统期末题库 第九期(完结)

LeetCode Hot100 刷题(第三期)

idea创建SpringBoot项目报错解决方案

数据库SQL语句(期末冲刺)

想看JavaB组填空题的伙伴们点点下方 👇

填空题

竞赛干货

算法竞赛字符串常用操作大全

蓝桥杯上岸必刷!!!(模拟/枚举专题)

蓝桥杯上岸必背!!! (第三期 DP)

蓝桥杯上岸必背!!!(第四期DFS)

蓝桥杯上岸必背!!!(第五期BFS)

蓝桥杯上岸必背!!!(第六期树与图的遍历)

蓝桥杯上岸必背!!!(第七期 最短路算法)

蓝桥杯上岸必背!!!(第八期 简单数论)

蓝桥杯上岸必刷!!!(进制、数位专题)

蓝桥杯上岸考点清单 (冲刺版)!!!

蓝桥杯上岸必背模板 (纯享版)

<think>好的,我现在需要回答用户关于统计子矩阵算法或实现方法的问。用户提到了蓝桥杯目,并引用了几个相关资源,包括前缀和的应用。首先,我得理解用户的需求:他们想了解如何高效统计满足特定条件的子矩阵数量,特别是矩阵和不超过K的情况。 首先,我应该回顾用户提供的引用内容。引用[1]提到统计子矩阵的和不超过K的问,引用[2]和[3]讨论了前缀和统计子矩阵中的应用,比如全1正方形或XY频数相等的矩阵。引用[4]则详细描述了枚举上下边界,然后利用双指针法处理左右边界的思路,这可能是优化的关键。 接下来,我需要整理常见的子矩阵统计方法。通常,暴力枚举所有可能的子矩阵时间复杂度极高,无法处理较大的矩阵。因此,必须采用优化策略。前缀和是常见的优化手段,可以快速计算子矩阵的和。用户提到的引用[2][3][4]都涉及前缀和,所以这应该是重点。 然后,考虑如何将问分解。根据引用[4],枚举上下边界(即确定子矩阵的起始行和结束行),然后处理列方向上的左右边界。这样可以减少计算量。在固定上下边界后,每一列的和可以压缩成一个一维数组,之后的问转化为在一维数组中寻找连续子数组的和不超过K的数量。这时候可以使用双指针法或者前缀和结合二分查找来优化。 例如,对于每个上下边界的组合,计算每列的总和,得到一个一维数组。然后,在这个一维数组中使用滑动窗口法(双指针)来统计符合条件的左右边界组合。这种方法的时间复杂度是O(N²M),其中N是行数,M是列数。如果N和M相差较大,可能需要进一步优化,比如当M较大时,可以转置矩阵,使得较小的维度被枚举。 此外,需要注意前缀和的正确计算方式。二维前缀和的计算公式是preSum[i][j] = preSum[i-1][j] + preSum[i][j-1] - preSum[i-1][j-1] + matrix[i][j]。但在这个问中,当枚举了上下边界后,可以将每列的和预先计算,从而将二维问转换为一维,这可能更高效。 现在,我需要将这些思路整理成一个清晰的步骤,并给出代码实现。可能需要先处理二维前缀和,然后优化枚举过程,使用双指针来减少重复计算。同时,要注意处理边界情况,比如矩阵元素可能为负数时,滑动窗口法是否适用。不过根据用户的问中的K的条件,可能假设所有元素都是非负的,否则滑动窗口无法直接应用,这时候需要改用其他方法,如前缀和的有序集合。 但是,在引用[4]中提到的方法假设了当右端点右移时,总和递增,这可能在元素非负的情况下成立。因此,可能需要明确问中矩阵的元素是否允许负数。用户的问中引用的例子可能都是非负的情况,比如统计1矩阵或者和不超过K,通常可能矩阵元素非负。所以假设元素非负,可以安全使用双指针法。 综上,步骤大致如下: 1. 预处理列方向的前缀和,或者对于每个上下边界组合,计算每列的和。 2. 将二维问转化为一维数组的子数组和不超过K的问。 3. 使用滑动窗口(双指针)法统计每个一维数组中的有效子数组数目。 4. 将所有上下边界的情况累加,得到总数。 现在,需要验证这个思路是否正确,并考虑可能的代码实现。例如,在Python中,如何高效实现列方向的前缀和?或者,可以在枚举上下边界时,逐行累加列的和,得到压缩的一维数组。 然后,编写示例代码,并进行测试。例如,针对一个示例矩阵,验证算法是否正确统计了符合条件的子矩阵数目。 可能还需要考虑时间复杂度的分析,确保算法在合理时间内运行。例如,对于N行,枚举上下边界是O(),而每个一维数组的处理是O(M),所以总复杂度为O(N²M),当N和M为1e3时,这样的复杂度可能无法通过,但用户的问可能数据范围较小,或者蓝桥杯目可能允许这样的复杂度。 最后,生成相关问,帮助用户进一步理解相关算法和优化方法。</think>### 统计子矩阵算法实现 #### 问分析 统计满足子矩阵和不超过给定整数$K$的个数,需在$N \times M$的矩阵中找到所有可能的子矩阵。直接暴力枚举所有子矩阵时间复杂度为$O(N^2M^2)$,无法处理较大矩阵,需通过**前缀和+双指针**优化。 --- #### 核心算法:二维前缀和压缩+滑动窗口 1. **预处理列前缀和** 对每列计算前缀和,便于快速计算任意上下边界之间的列累加值。 例如:第$j$列的第$x$行到第$y$行的和为$sum[j] = col\_prefix[y][j] - col\_prefix[x-1][j]$。 2. **枚举上下边界** 遍历所有可能的行范围$[x, y]$(共$O(N^2)$种组合),将二维问压缩为一维数组$row\_sum$,其中$row\_sum[j]$表示第$j$列在行范围$[x, y]$的总和。 3. **滑动窗口统计一维子数组** 在$row\_sum$数组中,用双指针$l$和$r$维护滑动窗口,计算窗口内子数组和是否$\leq K$。若总和超过$K$,则左指针右移,否则累加符合条件的子矩阵数量。 时间复杂度:$O(N^2 \cdot M)$。 --- #### 代码实现(Python) ```python def count_submatrix(matrix, K): n, m = len(matrix), len(matrix[0]) # 预处理列前缀和 col_prefix = [[0] * m for _ in range(n+1)] for j in range(m): for i in range(n): col_prefix[i+1][j] = col_prefix[i][j] + matrix[i][j] count = 0 # 枚举上下边界x和y for x in range(n): for y in range(x, n): row_sum = [0] * m for j in range(m): row_sum[j] = col_prefix[y+1][j] - col_prefix[x][j] # 滑动窗口处理一维数组 left = 0 current_sum = 0 for right in range(m): current_sum += row_sum[right] while current_sum > K and left <= right: current_sum -= row_sum[left] left += 1 count += right - left + 1 # 统计以right结尾的合法子数组数量 return count ``` --- #### 关键优化点 1. **列前缀和压缩**:将二维矩阵按列压缩,避免重复计算列累加值[^4]。 2. **滑动窗口法**:在行范围固定后,通过双指针将一维子数组和问的时间复杂度从$O(M^2)$降为$O(M)$。 --- #### 示例验证 假设矩阵$A = [[1, 2], [3, 4]]$,$K=3$: - 子矩阵`[1]`、`[1,2]`、`[3]`均满足条件。 - 算法计算结果应为3。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

寸 铁

感谢您的支持!

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

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

打赏作者

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

抵扣说明:

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

余额充值