暴力题解
乍一看题目的解体方法是,二维前缀和+二分查找,但是这样的话只能拿到70%的分
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD = 1000000007;
const int N = 505;
ll m, n, k;
ll a[N][N];
ll ans = 0;
ll getsum(int i, int j, int x, int y) {
return a[x][y] - a[i - 1][y] - a[x][j - 1] + a[i - 1][j - 1];
}
int main() {
cin >> m >> n >> k;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
cin >> a[i][j];
a[i][j] += a[i - 1][j] - a[i - 1][j - 1] + a[i][j - 1];
}
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (getsum(i, j, i, j) > k) continue;
for (int x = i; x <= m; x++) {
int l = j, r = n;
while (l < r) {
int mid = (l + r + 1) >> 1;
if (getsum(i, j, x, mid) > k) r = mid - 1;
else l = mid;
}
if (getsum(i, j, x, l) > k) break;
ans += l - j + 1;
}
}
}
cout << ans << endl;
return 0;
}
真正题解(一维前缀+双指针滑动窗口)
我们枚举上边界和下边界,即当前枚举矩阵是处在第x行(上边界),和y(下边界)之间的。接着枚举矩形的右端点,我们可以发现,每次当右端点向右移动的时候,矩形的面积是单调递增的,此时如果矩形的面积大于K,则不合要求,固定右端点,此时我们将当前矩形的左端点也向右移动,矩形的面积单调变小,直到举行面积再次小于K,停止移动。
那么我们怎么才能得到子矩阵的数目呢?
其实就是右边界到达临界值的时候,减去左边界再加1,得到的数就是我们循环一次能得到的子矩阵的数量。
#include<bits/stdc++.h>
using namespace std;
int n,m,k;
const int maxn=505;
int g[maxn][maxn];//原数组
int sum[maxn][maxn];//前缀和数组
int main()
{
ios::sync_with_stdio(false) ;//这一行代码的作用是给cin和cout加速
cin>>n>>m>>k;
for(int i = 1; i <= n ; i++)
{
for(int j = 1; j <= m ; j++)
{
cin>>g[i][j];
// 按列的前缀和放入sum数组中
sum[i][j] = g[i][j] + sum[i-1][j];
}
}
// 验证sum数组中的数是不是按列的前缀和
// for(int i = 1; i <= n ; i++)
// {
// for(int j = 1; j <= m ; j++)
// {
// cout<<sum[i][j]<<' ';
//
// }
// cout<<endl;
// }
long long m_count = 0;
//把和想象成四个点围成的矩形的面积
for(int x = 1; x <= n; x++)//x是上边界
{
for(int y = x; y <= n; y++)//y是下边界
{
for(int r = 1, l = 1, s = 0; r <= m; r++)//r是右边界,l是左边界
{
s += sum[y][r] - sum[x - 1][r];
while(s > k && l < r)
{
s = s - sum[y][l] + sum[x - 1][l];
l++;
}
if(s <= k){
m_count += r - l + 1;
}
}
}
}
cout << m_count << endl;
return 0;
}
总结
在做这一题之前最好去学习一下一维前缀和二维前缀的算法,搞清楚前缀和是怎么得出来的,在纸上写写画画,不然很难理解。