最大子矩阵

GitHub跳转原题关系图返回列表

枚舉子矩陣上下邊界,把列壓成一位數組後用 Kadane 求最大子段和,O(N^3) 解決二維最大子矩陣。

OJ: roj

题目 ID: 1282

难度:提高

标签:最大子段和Kadane二維問題枚舉

创建: 2026-09-30 03:02

更新: 2026-10-07 11:01

形式化题目

给定一个 N×NN \times N 的整数矩阵 AA。要求选出一个非空的连续子矩阵(行区间、列区间均连续),使其所有元素之和最大。

形式化地,求

max⁡0⩽top⩽bottom<N max⁡0⩽l⩽r<N ∑i=topbottom∑j=lrAi,j \max_{0 \leqslant top \leqslant bottom < N}\ \max_{0 \leqslant l \leqslant r < N}\ \sum_{i=top}^{bottom}\sum_{j=l}^{r} A_{i,j}

其中至少选取一个元素。

样例

下面表格中加粗格子组成了样例的最大子矩阵(第 1~3 行、第 1~2 列),其和为 1515。

0 1 2 3
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2

正解

思路

二维最大子矩阵可以从一维最大子段和扩展而来。

  • 固定子矩阵的上边界 toptop 和下边界 bottombottom,把这两行之间的每一列从上到下求和,得到一个长度为 NN 的一维数组 colcol。
  • 此时任意连续列区间 [l,r][l, r] 对应的子矩阵和,恰好等于 colcol 中子段 col[l..r]col[l..r] 的和。
  • 因此问题转化为求该一维数组的最大子段和,可用 Kadane 算法在 O(N)O(N) 时间内完成。
  • 遍历所有 top⩽bottomtop \leqslant bottom 的上下边界组合,全局最大的结果即为答案。

列压缩示意图

以样例为例,当 top=1top = 1、bottom=3bottom = 3 时,把第 1 到第 3 行按列相加:

col=(9+(−4)+(−1), 2+1+8, −6+(−4)+0, 2+1+(−2))=(4,11,−10,1) \begin{aligned} col &= (9 + (-4) + (-1),\ 2 + 1 + 8,\ -6 + (-4) + 0,\ 2 + 1 + (-2)) \\ &= (4, 11, -10, 1) \end{aligned}

对该 colcol 跑最大子段和,最优取前两列,得到 4+11=154 + 11 = 15,对应子矩阵

[92−41−18] \begin{bmatrix} 9 & 2 \\ -4 & 1 \\ -1 & 8 \end{bmatrix}

代码

Python 版:

python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-09-30 03:03
# update_at: 2026-09-30 03:03

import sys


def max_subarray(arr: list[int]) -> int:
    """一维最大子段和:Kadane。"""
    best = cur = arr[0]
    for x in arr[1:]:
        cur = max(x, cur + x)
        best = max(best, cur)
    return best


def solve() -> None:
    data = list(map(int, sys.stdin.buffer.read().split()))
    if not data:
        return
    it = iter(data)
    n = next(it)
    a = [[next(it) for _ in range(n)] for _ in range(n)]  # n x n 矩阵

    ans = a[0][0]  # 至少选一个元素
    col = [0] * n
    for top in range(n):               # 枚举子矩阵上边界
        col = [0] * n                   # 列前缀和归零
        for bottom in range(top, n):     # 枚举子矩阵下边界
            for c in range(n):
                col[c] += a[bottom][c]  # 把第 bottom 行加入每一列的累加和
            ans = max(ans, max_subarray(col))  # 当前条带内的最大子段和

    print(ans)


if __name__ == "__main__":
    solve()

C++ 版(同一算法):

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-10-04 23:49
 * update_at: 2026-10-04 23:49
 */
#include <iostream>
#include <algorithm>

typedef long long ll;

const ll MAXN = 105; // 题目为 N x N 矩阵,N 一般不超过 100,留一点余量

ll n;
ll a[MAXN][MAXN]; // 原始矩阵
ll col[MAXN];     // 枚举上下边界时,每一列在 [top, bottom] 内的累加和

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    std::cin >> n;
    for (ll i = 0; i < n; ++i)
        for (ll j = 0; j < n; ++j)
            std::cin >> a[i][j];

    ll ans = a[0][0]; // 答案初始化为某个元素,保证子矩阵非空

    // 枚举子矩阵的上边界 top
    for (ll top = 0; top < n; ++top) {
        for (ll j = 0; j < n; ++j) col[j] = 0; // 每换一个上边界,列和清零

        // 枚举子矩阵的下边界 bottom,同时把第 bottom 行累加进列和
        for (ll bottom = top; bottom < n; ++bottom) {
            for (ll j = 0; j < n; ++j)
                col[j] += a[bottom][j];

            // 对一维数组 col 做 Kadane 求最大子段和
            ll cur = col[0];
            ll best = col[0];
            for (ll j = 1; j < n; ++j) {
                cur = std::max(col[j], cur + col[j]);
                best = std::max(best, cur);
            }
            ans = std::max(ans, best);
        }
    }

    std::cout << ans << "\n";
    return 0;
}

复杂度

  • 时间复杂度:枚举上边界 O(N)O(N)、枚举下边界 O(N)O(N)、列压缩 O(N)O(N)、Kadane O(N)O(N),总复杂度为 O(N3)O(N^3)。
  • 空间复杂度:存储矩阵 O(N2)O(N^2),列压缩数组 O(N)O(N),总复杂度为 O(N2)O(N^2)。

总结

本题的核心观察是:二维子矩阵的搜索可以拆成“枚举行区间”与“一维最大子段和”两个子问题。通过列压缩把每一对上下边界对应成一个一维数组,再用 Kadane 算法在 O(N)O(N) 时间内求出最佳列区间,最终得到 O(N3)O(N^3) 的整体解法。实现时需注意 Kadane 初始值必须取数组第一个元素,以保证子段(也就是子矩阵)非空。