题解:学而思编程 最大子段乘积

【题目来源】

学而思编程:最大子段乘积

【题目描述】

给出一个数列,要求找出两个不相邻的非空子段,使得两个子段和的乘积最大。

例如对数列 \(2,3,1,9,−2,3\),可取子段 \(2,3\)\(9,−2,3\),两段的和分别是 \(2+3=5\)\(9+(−2)+3=10\),乘积为 \(50\)

【输入】

\(1\) 行,\(1\) 个正整数 \(n\)

\(2\) 行,\(n\) 个整数 \(a_1,a_2,⋯ ,a_n\)

【输出】

输出最大的乘积。

【输入样例】

6
2 3 1 9 -2 3

【输出样例】

50

【核心思想】

  1. 问题分析:给定长度为 \(n\) 的数组 \(a\),要求找出两个不相邻的非空子段,使得两个子段和的乘积最大。由于负数 × 负数 = 正数,需要考虑最大和最小两种情况。这是一个线性DP问题,关键在于如何高效计算前后缀的极值。

  2. 算法选择

    • 两次Kadane算法:分别计算前缀和后缀的最大/最小连续子段和
    • 前后缀极值数组:维护四个数组:前缀最大、前缀最小、后缀最大、后缀最小
    • 枚举分割点:遍历所有可能的分割位置,计算两段乘积的最大值
  3. 关键步骤

    • 从前向后计算(计算前缀连续子段和):
      • s1[i] = max(a[i], s1[i-1] + a[i]):以 \(i\) 结尾的最大连续子段和
      • s2[i] = min(a[i], s2[i-1] + a[i]):以 \(i\) 结尾的最小连续子段和
    • 维护前缀极值
      • f1[i] = max(f1[i-1], s1[i]):前缀最大子段和(位置 \(1\)\(i\) 中的最大值)
      • f2[i] = min(f2[i-1], s2[i]):前缀最小子段和(位置 \(1\)\(i\) 中的最小值)
    • 从后向前计算(计算后缀连续子段和):
      • s1[i] = max(a[i], s1[i+1] + a[i]):以 \(i\) 开头的最大连续子段和
      • s2[i] = min(a[i], s2[i+1] + a[i]):以 \(i\) 开头的最小连续子段和
    • 维护后缀极值
      • g1[i] = max(g1[i+1], s1[i]):后缀最大子段和(位置 \(i\)\(n\) 中的最大值)
      • g2[i] = min(g2[i+1], s2[i]):后缀最小子段和(位置 \(i\)\(n\) 中的最小值)
    • 枚举分割点\(i\)\(2\)\(n-1\)):
      • ans = max(ans, f1[i-1] × g1[i+1])(最大 × 最大)
      • ans = max(ans, f2[i-1] × g2[i+1])(最小 × 最小,负负得正)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n)\),四次线性遍历(前向、后向、前缀极值、后缀极值、枚举分割点)
    • 空间复杂度:\(O(n)\),六个辅助数组(s1, s2, f1, f2, g1, g2
  5. 线性DP的核心思想

    • Kadane算法:在 \(O(n)\) 时间内求最大/最小连续子段和
    • 同时维护最大和最小:因为最小负数相乘可能得到更大的正数
    • 前后缀分解:将问题分解为前缀和后缀两部分,枚举分割点组合
    • 极值数组:通过维护前缀/后缀极值数组,快速查询任意分割点的最优解
    • 适用于最大子段和、子段乘积、区间最值类问题

【算法标签】

线性DP-一维

【代码详解】

#include<iostream>
#include<cstring>
using namespace std;
const int N = 1e5 + 5;
int n, a[N], s1[N], s2[N], f1[N], f2[N], g1[N], g2[N];
long long ans = -1e18;  // 答案初始化为负无穷

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }

    // 从前向后计算
    for (int i = 1; i <= n; i++)
    {
        s1[i] = max(a[i], s1[i - 1] + a[i]);  // 最大连续子段和
        s2[i] = min(a[i], s2[i - 1] + a[i]);  // 最小连续子段和
    }

    // 初始化
    memset(f1, -0x3f, sizeof(f1));
    memset(g1, -0x3f, sizeof(g1));
    memset(f2, 0x3f, sizeof(f2));
    memset(g2, 0x3f, sizeof(g2));

    // 前缀最大值/最小值
    for (int i = 1; i <= n; i++)
    {
        f1[i] = max(f1[i - 1], s1[i]);  // 前缀最大子段和
        f2[i] = min(f2[i - 1], s2[i]);  // 前缀最小子段和
    }

    // 从后向前计算
    for (int i = n; i >= 1; i--)
    {
        s1[i] = max(a[i], s1[i + 1] + a[i]);  // 后缀最大子段和
        s2[i] = min(a[i], s2[i + 1] + a[i]);  // 后缀最小子段和
    }

    // 后缀最大值/最小值
    for (int i = 1; i <= n; i++)
    {
        g1[i] = max(g1[i + 1], s1[i]);  // 后缀最大子段和
        g2[i] = min(g2[i + 1], s2[i]);  // 后缀最小子段和
    }

    // 枚举分割点i
    for (int i = 2; i < n; i++)
    {
        ans = max(ans, 1ll * f1[i - 1] * g1[i + 1]);  // 最大×最大
        ans = max(ans, 1ll * f2[i - 1] * g2[i + 1]);  // 最小×最小
    }
    cout << ans;
    return 0;
}

【运行结果】

6
2 3 1 9 -2 3
50
posted @ 2026-06-14 10:47  团爸讲算法  阅读(3)  评论(0)    收藏  举报