boxmoe_header_banner_img

欢迎来到KAZAMATA'S WEBSITE

加载中

统计子矩阵


avatar
kazamata 2026年11月7日 2026年11月7日 35

题目描述
给定一个 N\times M 的矩阵 A,请你统计有多少个子矩阵(最小 1\times1,最大 N\times M)满足子矩阵中所有数的和不超过给定的整数 K?

输入格式
第一行包含三个整数 N、M 和 K。
之后 N 行每行包含 M 个整数,代表矩阵 A。

输出格式
一个整数代表答案。

样例输入
3 4 10
1 2 3 4
5 6 7 8
9 10 11 12

样例输出
19

样例说明
满足条件的子矩阵一共有 19 个,包含:
大小为 1\times1 的有 10 个。
大小为 1\times2 的有 3 个。
大小为 1\times3 的有 2 个。
大小为 1\times4 的有 1 个。
大小为 2\times1 的有 3 个。

评测用例规模与约定
对于 30\% 的数据,N,M\le 20。
对于 70\% 的数据,N,M\le 100。
对于 100\% 的数据,1\le N,M\le 500,0\le A_{ij}\le 1000,1\le K\le 250000000。

精炼题目:

给定一个矩阵,知道这个矩阵的行列信息,让你求和能够小于k的子矩阵的数量(子矩阵行列是连续的)

思路:

我要关心的是什么:

我只关心每一个子矩阵的和与k的关系

枚举?

让我们来讨论枚举的可能性,一个N*M的矩阵,它的子矩阵为P*Q(P<=N,Q<=M),一个N行得到矩阵,选连续的上下边界top和bottom(top\le bottom),方案数为C_{N+1}^2 = \dfrac{N(N+1)}{2}

(把行数列在数轴上,求连续区间,就是在N个元素里面找到两个不同的分割线,N行就会有N+1个分割线,用组合的方式就能得到上式)

同理,列选左右边界,有

C_{M+1}^2 = \dfrac{M(M+1)}{2}

总的子矩阵数为

 

\mathbf{Total} = \dfrac{N(N+1)}{2}\cdot \dfrac{M(M+1)}{2}

把极限数值N=M=500带入

\dfrac{500\times 501}{2} \times \dfrac{500\times 501}{2} \approx 156\ \text{亿}

需要进行156亿次计算,肯定会超过1秒钟的时间限制,即枚举不可能成功

那要怎么做?

我只对一维数组的处理比较熟悉,如果说题目是让我求一维数组里面的子区间的和不超过k的话,我或许能通过双指针的方式求得,但是这里是一个二维数组,有没有什么办法能把二维数组转化为一维数组呢,我要求的是矩阵的子矩阵的和,我不关心子矩阵里面的一个个元素,我只关心他们的和,所以说我可以抛弃掉元素因素,只留下和因素,那我可以把一个连续行区间的子矩阵i~j,每一列都相应加起来,不就能得到一个只有每列和的一个一维矩阵了:

图示:屏幕截图 2026-11-07 114910

那么我们就可以使用压缩矩阵的方式,把每一个子矩阵都压缩成一个一维矩阵,然后把这个一维矩阵输入到判断函数中来判断是否小于k.

由此引出下面几个问题:

1.怎么求子矩阵

2.怎么压缩子矩阵

3.判断函数怎么写

 

怎么求子矩阵?

要知道矩阵是有行有列的,那就有上界,下界,左界,右界,我们令上界top为0,下界bottom从top开始,遍历到总行数的位置,比如说top从0开始,那么在top=0的时候,bottom依次遍历0,1,2,3…N,就能获得[0,0],[0,1],[0,2]…[0,n]区间的子矩阵,当内层循坏跑完N之后,top转移到1,并从中获得[1,1],[1,2]…[1,n]区间的子矩阵,但是我们发现,获得的子矩阵并不是完整的,它只有行的区别,而他们列是没有区别的,所以说我们需要先压缩子矩阵,得到行压缩子矩阵之后,再对列进行处理,这里我们放到判断函数中去处理

怎么压缩子矩阵?

压缩子矩阵并不困难,我们先令一个空的数组sum[M],这里数组元素的数量要等于列数量,使其能够放入全部列的求和,当bottom向下移动一单位时,我们就把sum每个对应位置的元素都+=bottom所在行的对应列元素,这样子在bottom向下移动的时候,sum每一个对应列的元素会不断加子矩阵下一行对应列的元素,最后输出的sum数组有且只有一行。

判断函数怎么写?

判断函数是对一维数组的判断,这里我们用到双指针的方法(数组元素全部 ≥ 0,可以使用双指针),定义一个左指针l,一个右指针r,核心规则是右指针在压缩矩阵中不断往前走,左指针也不断往前走,两者都不回头,一开始r=l=0,定义sum为窗口的和,计数器(计一个压缩矩阵中子矩阵和小于k的数)为cnt,这个判断由一个for循环和一个while循环组成,for循环负责控制r的步进,while循环负责判断窗口和是否大于k,一开始r=0,进入for循环,sum+=sum[r],进入while判断,如果窗口和不大于k,while就不进入此时l=0,计数器cnt=0+1,r再次进入for循环,直到窗口和大于k,进入while循环,让l步进,窗口和减去l步进时各个元素的值,再次判断窗口和是否大于k,不是则退出循环,这里cnt+= r-l+1是因为,当r=5不变时,l步进l=1步使得sum<k,即[1,5]区间和是小于k的,那么显然[2,5],[3,5],[4,5][5,5]都是小于k的,其中包含了5-1+1一共五个合法子区间,所以cnt+=r-l+1. 

经过一系列的操作之后,函数返回cnt的值,每一行的压缩矩阵都进入函数一边,再把结果都加入到num中就能拿到答案.

下面是代码展示:

<code>#include <stdio.h>
long long int matrix[500][500];
long long int check(long long sum[], long long int k, long long int m)
{
    long long cnt = 0;
    long long s = 0;
    int l = 0;
    for(int r = 0; r < m; r++)
    {
        s += sum[r];
        while(s > k)
        {
            s -= sum[l];
            l++;
        }
        cnt += r - l + 1;
    }
    return cnt;
}

int main(void)
{
	long long int n,m,k,num=0;
	scanf("%lld %lld %lld",&n,&m,&k);
	
	int top,bottom;//上界与下界
	int x,y;
	for(x=0;x<n;x++)
	{
		for(y=0;y<m;y++)
		{
			scanf("%lld",&matrix[x][y]);
		}
	}
	
	for(top=0;top<n;top++)
	{
		long long int sum[500] = {0};//压缩数组
		int temp;
		for(bottom = top;bottom<n;bottom++)
		{
			for(temp=0;temp<m;temp++)
			{
				sum[temp]+=matrix[bottom][temp];
			}
			num+=check(sum,k,m);
		}
		
	} 
	printf("%lld",num);
	return 0;
	 
}</code>
如果你有更好的算法,不妨点击此处提交
提交到 KAZAMATA DEV C++

无需登录:打开后填写昵称即可提交,并计入贡献排行榜

贡献排行榜

排行榜加载中…
上一次更新已经跑远了 计算中...
(‾◡◝) 本内容里的一些消息,可能已经跟不上时间啦~


评论(0)

查看评论列表

暂无评论


发表评论

表情 颜文字
插入代码
后退
前进
刷新
复制
粘贴
全选
删除
返回首页
0%
目录
顶部
底部
文章导读