题解:学而思编程 最大子段乘积
【题目来源】
学而思编程:最大子段乘积
【题目描述】
给出一个数列,要求找出两个不相邻的非空子段,使得两个子段和的乘积最大。
例如对数列 \(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
【核心思想】
-
问题分析:给定长度为 \(n\) 的数组 \(a\),要求找出两个不相邻的非空子段,使得两个子段和的乘积最大。由于负数 × 负数 = 正数,需要考虑最大和最小两种情况。这是一个线性DP问题,关键在于如何高效计算前后缀的极值。
-
算法选择:
- 两次Kadane算法:分别计算前缀和后缀的最大/最小连续子段和
- 前后缀极值数组:维护四个数组:前缀最大、前缀最小、后缀最大、后缀最小
- 枚举分割点:遍历所有可能的分割位置,计算两段乘积的最大值
-
关键步骤:
- 从前向后计算(计算前缀连续子段和):
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])(最小 × 最小,负负得正)
- 从前向后计算(计算前缀连续子段和):
-
时间/空间复杂度:
- 时间复杂度:\(O(n)\),四次线性遍历(前向、后向、前缀极值、后缀极值、枚举分割点)
- 空间复杂度:\(O(n)\),六个辅助数组(
s1,s2,f1,f2,g1,g2)
-
线性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
浙公网安备 33010602011771号