赞
踩
我们知道,只要确定了矩阵的左上顶点和右下顶点,一个矩阵就被固定了。因此,我们可以遍历这两个顶点,达到遍历所有子矩阵的目的,复杂度会达到
O
(
N
2
∗
M
2
)
O(N^2*M^2)
O(N2∗M2)。确定了子矩阵,就要判断子矩阵的值是否不大于
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(N2∗M2),会 LTE。
压缩维度:我们可以把二维矩阵压缩至一维:画两条线,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^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 单调递增。
利用单调性,我们可以得到以下结果:
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; } }
#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; }
完
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。