最大子段和及其变式

最大子段和

题目描述

给出一个长度为 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 n2×103
  • 对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 2 × 1 0 5 1 \leq n \leq 2 \times 10^5 1n2×105 − 1 0 4 ≤ a i ≤ 1 0 4 -10^4 \leq a_i \leq 10^4 104ai104

题解:

#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 1n120

题解:

#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;
}

这题其实是上一题的变式,如果直接暴力枚举每一种情况的可能的话,时间一定会爆掉。。。

所以我们必须考虑一种更优的写法。我在题解里学习到一种压缩矩阵的思想。

结合刚才的最大子段和的思想考虑。我们如果把每一列的方格看成一个长方形,那么求每一行的最大子段和不就是求出了最大加权矩形吗?

代码思路:

  1. 在输入的同时求出每一列的前缀和,方便等会压缩矩阵。
  2. 外层循环枚举压缩矩阵的最下方是第几行,第二层循环枚举压缩矩阵的高,第三层循环在横轴方向上求最大子段和
  3. 每求出一行压缩矩阵的最大子段和就比较,更新最大值。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值