一、题目描述
二、算法简析
2.1 二维前缀和
我们知道,只要确定了矩阵的左上顶点和右下顶点,一个矩阵就被固定了。因此,我们可以遍历这两个顶点,达到遍历所有子矩阵的目的,复杂度会达到 \(O(N^2*M^2)\)。确定了子矩阵,就要判断子矩阵的值是否不大于 \(K\)。 如何能高效地得到子矩阵的值呢?答案是二维前缀和。
与普通的前缀和不同,二维前缀和 \(\text{psum[i][j]}=\) 左上顶点 \((1, 1)\)、右下顶点 \((i, j)\) 确定的子矩阵的值。通过以下表达式,可以得到二维前缀和:
有了二维前缀和,就可以以 \(O(1)\) 确定左上角 \((x1, y1)\)、右下角 \((x2, y2)\) 的子矩阵的值:
\[\text{matrix\_val = psum[x2][y2] - psum[x1 - 1][y2] - psum[x2][y1 - 1] + psum[x1 - 1][y1 - 1]} \]但是,该算法的复杂度仍然有 \(O(N^2*M^2)\),会 LTE。
2.2 压缩维度 + 双指针
压缩维度:我们可以把二维矩阵压缩至一维:画两条线,high
表示矩阵上界(左上点只能在该行)、low
表示矩阵下界(右下点只能在该行)。因此,由 high
和 low
确定的子矩阵只能由列矩阵组合而成,所以按列压缩,即按列求和。
通过遍历 high
和 low
,我们可以得到所有组成子矩阵的列矩阵。
双指针:通过上文的压缩,我们得到了“子矩阵的零件”。为了得到该情况下的所有子矩阵,肯定要用双指针遍历压缩数组,得到所有组合方式。
int B[4]; // 压缩后的结果
for (int i = 0; i < 4; i++)
for (int j = i; j < 4; j++)
\\ ...
显然,指针 j
发生了回溯,导致复杂度达到了 \(O(n^2)\)。如何避免发生回溯呢?利用单调性,我们可以把复杂度降为 \(O(n)\)。
我们规定 \(\text{area(left, right) = B[left] + B[left + 1] + ... + B[right]}\)。
若 \(\text{area(left, right) <= K}\) 且 \(\text{left + 1 <= right}\),则 \(\text{area(left + 1, right) <= K}\)。
若 \(\text{area(left, right) > K}\) 且 \(\text{right + 1 <= M}\),则 \(\text{area(left, right + 1) > K}\)。
显然,\(\text{area(left, right)}\) 随 \(\text{left}\) 单调递减,随 \(\text{right}\) 单调递增。
利用单调性,我们可以得到以下结果:
- 1、随 \(\text{left}\) 单调递减,若 \(\text{area(left, right) <= K}\),则一共有 \(\text{right - left + 1}\) 种组合方式。
- 2、我们只需要遍历
right
就能得到所有子矩阵。因为单调性,若 \(\text{area(left, right) > K}\),只需要 \(\text{left++}\),直到 \(\text{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;
}
完
标签:right,int,text,矩阵,psum,统计,left From: https://www.cnblogs.com/hoyd/p/18043219