枚舉子矩陣上下邊界,把列壓成一位數組後用 Kadane 求最大子段和,O(N^3) 解決二維最大子矩陣。
OJ: roj
题目 ID: 1282
难度:提高
标签:最大子段和Kadane二維問題枚舉
创建: 2026-09-30 03:02
更新: 2026-10-07 11:01
形式化题目
给定一个
形式化地,求
其中至少选取一个元素。
样例
下面表格中加粗格子组成了样例的最大子矩阵(第 1~3 行、第 1~2 列),其和为
| 0 | 1 | 2 | 3 |
|---|---|---|---|
| 0 | -2 | -7 | 0 |
| 9 | 2 | -6 | 2 |
| -4 | 1 | -4 | 1 |
| -1 | 8 | 0 | -2 |
正解
思路
二维最大子矩阵可以从一维最大子段和扩展而来。
- 固定子矩阵的上边界
和下边界 ,把这两行之间的每一列从上到下求和,得到一个长度为 的一维数组 。 - 此时任意连续列区间
对应的子矩阵和,恰好等于 中子段 的和。 - 因此问题转化为求该一维数组的最大子段和,可用 Kadane 算法在
时间内完成。 - 遍历所有
的上下边界组合,全局最大的结果即为答案。
列压缩示意图
以样例为例,当
对该
代码
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;
}复杂度
- 时间复杂度:枚举上边界
、枚举下边界 、列压缩 、Kadane ,总复杂度为 。 - 空间复杂度:存储矩阵
,列压缩数组 ,总复杂度为 。
总结
本题的核心观察是:二维子矩阵的搜索可以拆成“枚举行区间”与“一维最大子段和”两个子问题。通过列压缩把每一对上下边界对应成一个一维数组,再用 Kadane 算法在