统计子矩阵

一、题目描述

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

二、算法简析

2.1 二维前缀和

我们知道,只要确定了矩阵的左上顶点和右下顶点,一个矩阵就被固定了。因此,我们可以遍历这两个顶点,达到遍历所有子矩阵的目的,复杂度会达到 O ( N 2 ∗ M 2 ) O(N^2*M^2) O(N2M2)。确定了子矩阵,就要判断子矩阵的值是否不大于 K K K。 如何能高效地得到子矩阵的值呢?答案是二维前缀和
与普通的前缀和不同,二维前缀和 psum[i][j] = \text{psum[i][j]}= psum[i][j]= 左上顶点 ( 1 , 1 ) (1, 1) (1,1)、右下顶点 ( i , j ) (i, j) (i,j) 确定的子矩阵的值。通过以下表达式,可以得到二维前缀和:

psum[i][j] = psum[i][j - 1] + psum[i - 1][j] - psum[i - 1][j - 1] + A[i][j] \text{psum[i][j] = psum[i][j - 1] + psum[i - 1][j] - psum[i - 1][j - 1] + A[i][j]} psum[i][j] = psum[i][j - 1] + psum[i - 1][j] - psum[i - 1][j - 1] + A[i][j]

有了二维前缀和,就可以以 O ( 1 ) O(1) O(1) 确定左上角 ( x 1 , y 1 ) (x1, y1) (x1,y1)、右下角 ( x 2 , y 2 ) (x2, y2) (x2,y2) 的子矩阵的值:

matrix_val = psum[x2][y2] - psum[x1 - 1][y2] - psum[x2][y1 - 1] + psum[x1 - 1][y1 - 1] \text{matrix\_val = psum[x2][y2] - psum[x1 - 1][y2] - psum[x2][y1 - 1] + psum[x1 - 1][y1 - 1]} matrix_val = psum[x2][y2] - psum[x1 - 1][y2] - psum[x2][y1 - 1] + psum[x1 - 1][y1 - 1]

但是,该算法的复杂度仍然有 O ( N 2 ∗ M 2 ) O(N^2*M^2) O(N2M2),会 LTE


2.2 压缩维度 + 双指针

压缩维度:我们可以把二维矩阵压缩至一维:画两条线,high 表示矩阵上界(左上点只能在该行)、low表示矩阵下界(右下点只能在该行)。因此,由 highlow 确定的子矩阵只能由列矩阵组合而成,所以按列压缩,即按列求和。
图1
通过遍历 highlow,我们可以得到所有组成子矩阵的列矩阵。

双指针:通过上文的压缩,我们得到了“子矩阵的零件”。为了得到该情况下的所有子矩阵,肯定要用双指针遍历压缩数组,得到所有组合方式。

int B[4];   // 压缩后的结果

for (int i = 0; i < 4; i++)
	for (int j = i; j < 4; j++)
		\\ ...

显然,指针 j 发生了回溯,导致复杂度达到了 O ( n 2 ) O(n^2) O(n2)。如何避免发生回溯呢?利用单调性,我们可以把复杂度降为 O ( n ) O(n) O(n)
我们规定 area(left, right) = B[left] + B[left + 1] + ... + B[right] \text{area(left, right) = B[left] + B[left + 1] + ... + B[right]} area(left, right) = B[left] + B[left + 1] + ... + B[right]
area(left, right) <= K \text{area(left, right) <= K} area(left, right) <= K left + 1 <= right \text{left + 1 <= right} left + 1 <= right,则 area(left + 1, right) <= K \text{area(left + 1, right) <= K} area(left + 1, right) <= K
area(left, right) > K \text{area(left, right) > K} area(left, right) > K right + 1 <= M \text{right + 1 <= M} right + 1 <= M,则 area(left, right + 1) > K \text{area(left, right + 1) > K} area(left, right + 1) > K
显然, area(left, right) \text{area(left, right)} area(left, right) left \text{left} left 单调递减,随 right \text{right} right 单调递增

利用单调性,我们可以得到以下结果:

  • 1、随 left \text{left} left 单调递减,若 area(left, right) <= K \text{area(left, right) <= K} area(left, right) <= K,则一共有 right - left + 1 \text{right - left + 1} right - left + 1 种组合方式。
  • 2、我们只需要遍历 right 就能得到所有子矩阵。因为单调性,若 area(left, right) > K \text{area(left, right) > K} area(left, right) > K,只需要 left++ \text{left++} left++,直到 area(left, right) <= K \text{area(left, right) <= K} area(left, right) <= K
int B[4];   // 压缩后的结果

int left = 1, right = 1;
ll tmp = 0;
for (; right <= 4; right++)
{
	tmp += B[right];
	if (tmp <= K)
		ans += right - left + 1;
	else
	{
		while (tmp > K)
		{
			tmp -= B[left];
			left++;	
		}
		ans += right - left + 1;
	}	
} 

三、AC代码

#include <bits/stdc++.h>

using namespace std;

const int MAX = 505;
typedef long long ll;

int A[MAX][MAX], N, M, K;
ll ans, psum[MAX][MAX], B[MAX];

int quickin(void)
{
	int ret = 0;
	char ch = getchar();
	while (ch < '0' || ch > '9')
		ch = getchar();
	while (ch >= '0' && ch <= '9' && ch != EOF)
	{
		ret = ret * 10 + ch - '0';
		ch = getchar();
	}
	return ret;
}

void init(void)
{
	for (int i = 1; i <= N; i++)
		for (int j = 1; j <= M; j++)
			psum[i][j] = psum[i - 1][j] + A[i][j];
}

void solve(void)
{
	for (int high = 1; high <= N; high++)
	{
		for (int low = high; low <= N; low++)
		{
			for (int i = 1; i <= M; i++)
				B[i] = psum[low][i] - psum[high - 1][i];
				
			int left = 1, right = 1;
			ll tmp = 0;
			for (; right <= M; right++)
			{
				tmp += B[right];
				if (tmp <= K)
					ans += right - left + 1;
				else
				{
					while (tmp > K)
					{
						tmp -= B[left];
						left++;	
					}
					ans += right - left + 1;
				}	
			} 
		}
	}
	
	cout << ans << endl;
}

int main()
{
	#ifdef LOCAL
	freopen("test.in", "r", stdin);
	#endif
	
	N = quickin(), M = quickin(), K = quickin();
	for (int i = 1; i <= N; i++)
		for (int j = 1; j <= M; j++)
			A[i][j] = quickin();
			
	init();
	
	solve();
	
	return 0;
}

  • 36
    点赞
  • 51
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
蓝桥杯统计矩阵c问题是一个经典的算法问题,可以通过编程实现。下面我将用300字中文回答如何实现。 我们可以使用二维前缀和的方法来解决这个问题。首先,我们定义一个二维数组prefixSum,用来存储原始矩阵a中每个位置(i,j)之前所有元素的和。 我们可以通过以下方式计算prefixSum: 1. 遍历矩阵a的每个位置(i,j),计算prefixSum[i][j]: - 如果i=0且j=0,则prefixSum[i][j] = a[i][j]; - 如果i=0且j≠0,则prefixSum[i][j] = prefixSum[i][j-1] + a[i][j]; - 如果i≠0且j=0,则prefixSum[i][j] = prefixSum[i-1][j] + a[i][j]; - 如果i≠0且j≠0,则prefixSum[i][j] = prefixSum[i-1][j] + prefixSum[i][j-1] - prefixSum[i-1][j-1] + a[i][j]。 2. 接下来,我们可以通过计算prefixSum来统计矩阵c的和。对于每个矩阵c,我们可以使用以下方式计算其和: - 定义矩阵c的左上角位置为(i1, j1),右下角位置为(i2, j2); - 如果(i1, j1)为原始矩阵a的左上角,则c的和为prefixSum[i2][j2]; - 如果(i1, j1)为原始矩阵a的第一行,则c的和为prefixSum[i2][j2] - prefixSum[i2][j1-1]; - 如果(i1, j1)为原始矩阵a的第一列,则c的和为prefixSum[i2][j2] - prefixSum[i1-1][j2]; - 其他情况下,c的和为prefixSum[i2][j2] - prefixSum[i1-1][j2] - prefixSum[i2][j1-1] + prefixSum[i1-1][j1-1]。 通过以上的方法,我们可以编写代码来实现这个算法。代码的时间复杂度为O(M*N),其中M和N分别为原始矩阵a的行数和列数。因此,我们可以通过这个方法高效地解决蓝桥杯统计矩阵c问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值