Official

D - 最大長方形部分和 / Maximum Rectangular Subarray Sum Editorial by MMNMM


まずは、次の問題について考えてみましょう。

高橋君は長さ \(N\) の整数列 \(A=(A _ 1,A _ 2,\ldots,A _ N)\) を持っています。この長さ \(1\) 以上の区間 \(\lbrack L,R\rbrack\) に対する \(A _ L+A _ {L+1}+\cdots+A _ R\) の最大値を求めてください。

この問題は、累積和を先頭から走査し、先頭からの min などを管理することで \(O(N)\) 時間で解くことができます(AWC0067 B なども参照してください)。

いま、\(\dfrac{M(M+1)}2\) 通りの \((c _ 1,c _ 2)\) の値を全探索することを考えます。 整数列 \(S=(S _ 1,S _ 2,\ldots,S _ N)\) を \[S _ i=A _ {i,c _ 1}+A _ {i,c _ 1+1}+\cdots+A _ {i,c _ 2}\] によって定めると、この \(S\) に対する上の問題の答えが \((c _ 1,c _ 2)\) を固定したときの元の問題の答えになっています。

あとは \(S\) の値を高速に求められればこの問題を解くことができます。 これは、\(A _ {i,j}\) の行ごとに累積和を求めておいたり、直前に探索した \((c _ 1,c _ 2)\) からの差分を計算するなどして \(O(M)\) 時間とできます。

実装例は以下のようになります。

#include <iostream>
#include <vector>
using namespace std;

int main() {
    int N, M;
    cin >> N >> M;

    vector A(N, vector<long>(M));
    for (vector<long>& v : A) {
        for (long& a : v) {
            cin >> a;
        }
    }

    // 横方向に累積和を取る
    for (vector<long>& v : A) {
        for (int i = 1; i < M; ++i) {
            v[i] += v[i - 1];
        }
        v.emplace(begin(v)); // 先頭に 0 を追加しておく
    }

    long ans = -10000; // 答えの下限は -10000
    for (long c1 = 0; c1 < M; ++c1) {
        for (long c2 = c1 + 1; c2 <= M; ++c2) { // (c1, c2) を全探索
            // S[i] := A[i][c2] - A[i][c1] に対して区間和の最大値を求める
            long prefix_min = 0, now = 0;
            for (int i = 0; i < N; ++i) {
                now += A[i][c2] - A[i][c1];
                ans = max(ans, now - prefix_min);
                prefix_min = min(prefix_min, now);
            }
        }
    }

    cout << ans << endl;
    return 0;
}
N, M = map(int, input().split())

A = [[0] + list(map(int, input().split())) for _ in range(N)]

# 横方向に累積和を取る
for v in A:
    for i in range(1, M + 1):
        v[i] += v[i - 1]

ans = -10000 # 答えの下限は -10000
for c1 in range(M):
    for c2 in range(c1 + 1, M + 1): # (c1, c2) を全探索
        # S[i] = A[i][c2] - A[i][c1] に対して区間和の最大値を求める
        prefix_min = 0
        now = 0
        for i in range(N):
            now += A[i][c2] - A[i][c1]
            ans = max(ans, now - prefix_min)
            prefix_min = min(prefix_min, now)

print(ans)

posted:
last update: