最大子段和
题目描述
给出一个长度为 n n n 的序列 a a a,选出其中连续且非空的一段使得这段和最大。
输入格式
第一行是一个整数,表示序列的长度 n n n。
第二行有 n n n 个整数,第 i i i 个整数表示序列的第 i i i 个数字 a i a_i ai。
输出格式
输出一行一个整数表示答案。
样例 #1
样例输入 #1
7
2 -4 3 -1 2 -4 3
样例输出 #1
4
提示
样例 1 解释
选取 [ 3 , 5 ] [3, 5] [3,5] 子段 { 3 , − 1 , 2 } \{3, -1, 2\} {3,−1,2},其和为 4 4 4。
数据规模与约定
- 对于 40 % 40\% 40% 的数据,保证 n ≤ 2 × 1 0 3 n \leq 2 \times 10^3 n≤2×103。
- 对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 2 × 1 0 5 1 \leq n \leq 2 \times 10^5 1≤n≤2×105, − 1 0 4 ≤ a i ≤ 1 0 4 -10^4 \leq a_i \leq 10^4 −104≤ai≤104。
题解:
#include <iostream>
#include <fstream>
#define ll long long
using namespace std;
int main()
{
fstream ifs("in.txt", ios::in);
int n; ifs >> n;
ll max_sum = -99999999999, sum = 0;
for (int i = 1; i <= n; i++) {
int t; ifs >> t;
sum = sum > 0 ? sum + t : t;
max_sum = max_sum > sum ? max_sum : sum;
}
ifs.close();
cout << max_sum;
return 0;
}
这个问题主要是采用了贪心和前缀和的思想,在求子段前缀和的同时一直不断地判断新的前缀和sum是否大于已有的最大值max_sum,但是如果sum减到0,那么证明已经不需要前面的子段了,可以重新开始一段新的子段求和。
为什么不需要了呢?
因为sum如果减到了0,还不如从现在的地方为起点再求子段。
假如现在sum减小到了-9,而后面紧接着的元素是999、9999、99999,这个时候如果不舍弃掉-9,那么得出的结果就是99999+9999+999还要再减去一个9,这样是不是不如不要这个-9,而且由于max_sum一直在不断地比较、更新,所以max_sum记录的值势必是当前的最大子段和,这样便不用担心你丢弃了没有用的-9之后,之前求最大子段和的努力付之东流。
——————————手动分割———————————
最大加权矩形
题目描述
为了更好的备战 NOIP2013,电脑组的几个女孩子 LYQ,ZSC,ZHQ 认为,我们不光需要机房,我们还需要运动,于是就决定找校长申请一块电脑组的课余运动场地,听说她们都是电脑组的高手,校长没有马上答应他们,而是先给她们出了一道数学题,并且告诉她们:你们能获得的运动场地的面积就是你们能找到的这个最大的数字。
校长先给他们一个 n × n n\times n n×n 矩阵。要求矩阵中最大加权矩形,即矩阵的每一个元素都有一权值,权值定义在整数集上。从中找一矩形,矩形大小无限制,是其中包含的所有元素的和最大 。矩阵的每个元素属于 [ − 127 , 127 ] [-127,127] [−127,127] ,例如
0 –2 –7 0
9 2 –6 2
-4 1 –4 1
-1 8 0 –2
在左下角:
9 2
-4 1
-1 8
和为 15 15 15。
几个女孩子有点犯难了,于是就找到了电脑组精打细算的 HZH,TZY 小朋友帮忙计算,但是遗憾的是他们的答案都不一样,涉及土地的事情我们可不能含糊,你能帮忙计算出校长所给的矩形中加权和最大的矩形吗?
输入格式
第一行: n n n,接下来是 n n n 行 n n n 列的矩阵。
输出格式
最大矩形(子矩阵)的和。
样例 #1
样例输入 #1
4
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
样例输出 #1
15
提示
1 ≤ n ≤ 120 1 \leq n\le 120 1≤n≤120
题解:
#include <iostream>
using namespace std;
int n, m[122][122] = { 0 };
int max(int a, int b) {
return a > b ? a : b;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
cin >> m[i][j];
//求列前缀和
m[i][j] += m[i - 1][j];
}
}
int ans = 0, sum = -99999999, max_sum = sum;
//第i行
for (int i = 1; i <= n; i++) {
//向上k行
for (int k = 1; k < i; k++) {
sum = -99999999, max_sum = sum;
//第j列
for (int j = 1; j <= n; j++) {
//求第j列的压缩后的矩形的面积
int f = m[i][j] - m[i - k][j];
sum = max(sum + f, f);
max_sum = max_sum > sum ? max_sum : sum;
}
if (max_sum > ans) ans = max_sum;
}
}
cout << ans;
return 0;
}
这题其实是上一题的变式,如果直接暴力枚举每一种情况的可能的话,时间一定会爆掉。。。
所以我们必须考虑一种更优的写法。我在题解里学习到一种压缩矩阵的思想。
结合刚才的最大子段和的思想考虑。我们如果把每一列的方格看成一个长方形,那么求每一行的最大子段和不就是求出了最大加权矩形吗?
代码思路:
- 在输入的同时求出每一列的前缀和,方便等会压缩矩阵。
- 外层循环枚举压缩矩阵的最下方是第几行,第二层循环枚举压缩矩阵的高,第三层循环在横轴方向上求最大子段和
- 每求出一行压缩矩阵的最大子段和就比较,更新最大值。