解法:二维前缀和+双指针
代码:
#include <iostream>
using namespace std;
typedef long long ll;
ll prefix[505][505], a[250010];
int main()
{
ll n, m, k, ans = 0; cin >> n >> m >> k;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++){
cin >> prefix[i][j];
prefix[i][j] += prefix[i-1][j] + prefix[i][j-1] - prefix[i-1][j-1];
//cout << prefix[i][j] << endl;
}
//for(int i = 1; i < n*m; i++) cout << prefix[i] << endl;
for(int i = 1; i <= n; i++)
for(int j = 1; j <= i; j++){
for(int l = 1, r = 1; r <= m; r++){
while(l <= r && prefix[i][r] - prefix[i][l-1] - prefix[j-1][r] + prefix[j-1][l-1] > k) l++;
if(l <= r) ans += r-l+1; //每一次循环,以r为区间,r每次必++
}
}
cout << ans;
return 0;
}
代码解释:
这段代码的核心是计算满足条件的子矩阵的数量。让我们逐步分析代码的逻辑,特别是你提到的 if (l <= r) ans += r - l + 1;
这一行。
代码逻辑概述
-
二维前缀和数组:
prefix[i][j]
表示从矩阵的左上角(1,1)
到当前位置(i,j)
的子矩阵的元素和。- 通过二维前缀和,可以在常数时间内计算任意子矩阵的和。
-
目标:
- 找出所有满足条件的子矩阵数量,其中子矩阵的和不超过给定的阈值
k
。
- 找出所有满足条件的子矩阵数量,其中子矩阵的和不超过给定的阈值
关键代码分析
外层循环
for (int i = 1; i <= n; i++) // 枚举子矩阵的上边界
for (int j = i; j <= n; j++) // 枚举子矩阵的下边界
- 这两层循环枚举了所有可能的子矩阵的上下边界。
i
是子矩阵的上边界,j
是子矩阵的下边界。
内层循环
for (int l = 1, r = 1; r <= m; r++) // 枚举子矩阵的右边界
- 这一层循环枚举了子矩阵的右边界
r
,同时用l
表示子矩阵的左边界。 l
和r
都是从 1 开始,r
逐渐向右扩展。
关键条件
while (l <= r && prefix[j][r] - prefix[j][l-1] - prefix[i-1][r] + prefix[i-1][l-1] > k) l++;
- 这一行的作用是通过滑动窗口的方式,找到满足条件的最小左边界
l
。 prefix[j][r] - prefix[j][l-1] - prefix[i-1][r] + prefix[i-1][l-1]
计算的是子矩阵(i, l)
到(j, r)
的和。- 如果当前子矩阵的和大于
k
,则需要缩小窗口,即将左边界l
向右移动,直到子矩阵的和不超过k
。
关键更新
if (l <= r) ans += r - l + 1;
- 这一行是关键所在。
- 在前面的
while
循环中,已经通过调整左边界l
,使得子矩阵(i, l)
到(j, r)
的和不超过k
。 - 如果
l <= r
,说明当前窗口是有效的,即存在满足条件的子矩阵。 r - l + 1
表示在当前的上下边界(i, j)
和右边界r
的情况下,所有可能的左边界l
的数量。
举例说明
假设当前上下边界为 (i, j)
,右边界为 r
,左边界 l
从 1 开始:
- 如果子矩阵
(i, 1)
到(j, r)
的和不超过k
,那么子矩阵(i, 2)
到(j, r)
、(i, 3)
到(j, r)
等等也一定满足条件。 - 因此,所有满足条件的子矩阵数量是从
l
到r
的所有可能的左边界数量,即r - l + 1
。
总结
if (l <= r) ans += r - l + 1;
这一行的作用是:
- 在当前上下边界
(i, j)
和右边界r
的情况下,统计所有满足条件的子矩阵数量。 - 这些子矩阵的左边界从
l
到r
,数量为r - l + 1
。