1.统计子矩阵

1.题目描述

给定一个 N × M N \times M N×M 的矩阵 A A A, 请你统计有多少个子矩阵 (最小 1 × 1 1 \times 1 1×1, 最大 N × M N \times M N×M) 满足子矩阵中所有数的和不超过给定的整数 K K K ?

2.输入格式

第一行包含三个整数 N , M N,M N,M K K K.

之后 N N N 行每行包含 M M M 个整数, 代表矩阵 A A A.

3.输出格式

一个整数代表答案。

4.样例输入

3 4 10
1 2 3 4
5 6 7 8
9 10 11 12

5.样例输出

19

6.数据范围

1 ≤ N , M ≤ 500 ; 0 ≤ A i j ≤ 1000 ; 1 ≤ K ≤ 250000000. 1≤N,M≤500;0≤Aij ≤1000;1≤K≤250000000. 1N,M500;0Aij1000;1K250000000.

7.原题链接

统计子矩阵

2.解题思路

首先,我们有一个查询子矩阵和的需求,这肯定是需要使用预处理二维前缀和来优化查询的。
未学习过的请跳转: 二维前缀和
但确定一个子矩阵,需要一个左上角坐标 ( x 1 , y 1 ) (x1,y1) (x1,y1)和一个右下角坐标 ( x 2 , y 2 ) (x2,y2) (x2,y2),如果进行暴力枚举的话复杂度将会是 O ( n 4 ) O(n^4) O(n4),这不可取。
考虑如何进行优化,对于 x 1 x1 x1 x 2 x2 x2 我们仍然进行暴力枚举。 然后考虑如何确定 y 1 y1 y1 y 2 y2 y2,这里我们用 L L L表示 y 1 y1 y1,用 R R R 表示 y 2 y2 y2
由于数组内不存在负数,不难发现随着指针 R R R右移, L L L也一定单调向右移动,这就转化成了一个一维数组内求子数组和不大与 K K K 的数目问题。枚举 R R R为右边界,此时子矩阵左上角坐标为 ( x 1 , L ) (x1,L) (x1,L),右下角坐标为 ( x 2 , R ) (x2,R) (x2,R),如求得总和大于 K K K L L L 向右移动,统计当前 R R R 作为右边界的答案数量为 R − L + 1 R-L+1 RL+1,这样双指针的做法复杂度为 O ( n ) O(n) O(n)。当然也可以二分得到 L L L 的位置,但是复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
这样使用双指针整体的复杂度为 O ( n 3 ) O(n^3) O(n3),如果使用二分的话为 O ( n 3 l o g n ) O(n^3logn) O(n3logn)会超时。 O ( n 3 ) O(n^3) O(n3)的复杂度在500下计算次数就达到了500x500x500=125,000,000
我们来分析一下为什么双指针能过但二分不能过,因为枚举 x 1 x1 x1 x 2 x2 x2 的操作严格意义的复杂度应该是 O ( n 2 2 ) O(\frac {n^2 }{2}) O(2n2),所以双指针算法实际运算次数应该是125,000,000/2=62,500,000是可以过的。二分会再乘一个 l o g 500 log500 log500,那肯定就爆掉了,经测试也二分做法也确实挂掉了。两个代码我都贴上
在这里插入图片描述

3.Ac_code

前缀和+双指针

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> PII;
#define pb(s) push_back(s);
#define SZ(s) ((int)s.size());
#define ms(s,x) memset(s, x, sizeof(s))
#define all(s) s.begin(),s.end()
const int inf = 0x3f3f3f3f;
const int mod = 1000000007;
const int N = 510;

LL n, m, k;
LL a[N][N], s[N][N];
//求子矩阵的和
LL sum(int x1, int y1, int x2, int y2) {
    return s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
}
void solve()
{
    cin >> n >> m >> k;
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            cin >> a[i][j];
        }
    }
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
        }
    }
    LL ans = 0;
    for (int i = 1; i <= n; ++i) {
        for (int j = i; j <= n; ++j) {
            for (int l = 1, r = 1; r <= m; ++r) {
                while (l <= r && sum(i, l, j, r) > k) l++;
                ans += r - l + 1;
            }
        }
    }
    cout << ans << '\n';
}

int main()
{
    ios_base :: sync_with_stdio(false);
    cin.tie(nullptr);
    int t = 1;
    while (t--)
    {
        solve();
    }
    return 0;
}

前缀和+二分(过80%数据)

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> PII;
#define pb(s) push_back(s);
#define SZ(s) ((int)s.size());
#define ms(s,x) memset(s, x, sizeof(s))
#define all(s) s.begin(),s.end()
const int inf = 0x3f3f3f3f;
const int mod = 1000000007;
const int N = 510;

LL n, m, k;
LL a[N][N], s[N][N];
//求子矩阵的和
LL sum(int x1, int y1, int x2, int y2) {
    return s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
}
void solve()
{
    cin >> n >> m >> k;
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            cin >> a[i][j];
        }
    }
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
        }
    }
    LL ans = 0;
    for (int i = 1; i <= n; ++i) {
        for (int j = i; j <= n; ++j) {
            for (int t = 1; t <= m; ++t) {
                int l = 1, r = t;
                while (l < r) {
                    int mid = l + r >> 1;
                    if (sum(i, mid, j, t) > k) l = mid + 1;
                    else r = mid;
                }
                if (sum(i, l, j, t)<=k) ans += t - l + 1;
            }
        }
    }
    cout << ans << '\n';
}

int main()
{
    ios_base :: sync_with_stdio(false);
    cin.tie(nullptr);
    int t = 1;
    while (t--)
    {
        solve();
    }
    return 0;
}
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐