51Nod-子段和系列题目【持续更新中】

1049最大子段和

题解:

O(n2)的暴力枚举是不可能让我们过的,所以只能优化,下面的算法可以优化到O(n),具体思路:
tmpSum:记录下实时的sum值,maxSum:记录下最大的子段和
子段和有三种情况:
正数:全为正数、有正有负
负数:全为负数
零:全为零
题目说所给整数均为负数时直接取0,所以maxSum初始化为0
算了,直接看我代码的注释吧(我可能也没说清楚,抱歉啦)。

代码:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll  INF = 0x3f3f3f3f3f3f3f3f;
const int PI = acos(-1);
const int E = exp(1);
const int MOD = 1e9+7;
const int MAX = 50000+5;
int n;
int a[MAX];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    while(cin >> n)
    {
        for(int i = 0; i < n; i++)
        {
            cin >> a[i];
        }
        ll tmpSum = 0;
        ll maxSum = 0;
        for(int i = 0; i < n; i++)
        {
        	//如果tmpSum > 0,不管a[i]是啥,都直接加上
        	//注:要是求最小子段和,这里换个符号即可
            if(tmpSum + a[i] > a[i])
            {
                tmpSum += a[i];
            }
            else//如果tmp <=0,就直接要a[i],因为要尽可能的大
            {
                tmpSum = a[i];
            }
            if(tmpSum > maxSum)
            {
                maxSum = tmpSum;
            }
        }
        cout << maxSum << endl;
    }
    return 0;
}

1050循环数组最大子段和

题解:

本题和上题的不同之处在于,数组是一个循环的,也就是会出现a[n-2]、a[n-1]、a[n]、a[1]、a[2]这种情况, 那么想一想最大子段和有哪些情况:
1.跟普通数组一样,最大子段和出现在例如,a[3]、a[4]、a[5]这种情况
2.最大子段和出现在例如,a[6]、a[7]、a[8]、a[1]、a[2]这种情况
如果是第一种情况,那么直接按上述的O(n)算法即可解决,
如果是第二种情况,考虑一下:
为啥最大子段和不是出现在中间(第一种情况)呢,而是出现在两端(第二种情况),那么一定是中间有一部分的和太小了
所以先求出最小子段和,然后 总和 - 最小子段和 即为两端的最大子段和。

代码:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll  INF = 0x3f3f3f3f3f3f3f3f;
const int PI = acos(-1);
const int E = exp(1);
const int MOD = 1e9+7;
const int MAX = 50000+5;
int n;
ll s[MAX];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    while(cin >> n)
    {
        ll sum = 0;
        for(int i = 0; i < n; i++)
        {
            cin >> s[i];
            sum += s[i];//求序列总和
        }
        ll tmpSum = 0;
        ll maxSum = 0;
        for(int i = 0; i < n; i++)//求普通数组(第一种情况)的最大子段和
        {
            if(s[i]+tmpSum > s[i])
            {
                tmpSum = s[i] + tmpSum;
            }
            else
            {
                tmpSum = s[i];
            }
            if(tmpSum > maxSum)
            {
                maxSum = tmpSum;
            }
        }
        tmpSum = 0;
        ll minSum = 0;
        for(int i = 0; i < n; i++)//最普通数组的最小子段和
        {
            if(s[i]+tmpSum < s[i])
            {
                tmpSum = s[i] + tmpSum;
            }
            else
            {
                tmpSum = s[i];
            }
            if(tmpSum < minSum)
            {
                minSum = tmpSum;
            }
        }
        //循环数组的最大子段和:
        //max(中间:普通数组最大子段和,两端:总和-普通数组最小子段和)
        cout << max(maxSum,sum-minSum) << endl;
    }
    return 0;
}

1065最小正子段和

题解:

刚开始我也是直接前缀和 + O(n2)的暴力枚举,结果肯定超时了,然后就想办法优化时间复杂度,刚开始自己也不理解,看别人的博客看懂的,
首先我先提两句,这个题目出的不严谨:
1.没有指明序列中整数的取值范围(那就直接取long long)
2.没有说一定存在正子段和(如果序列中全部都是负数或者0呢,那该怎么处理,也没说,那就只能当作测试数据一定存在正子段和)
然后继续思考:
最小正子段和一定出现在序列中的某个子段,而且是所有正子段中和最小的,假设左端点是i,右端点是j,那么presum[j] - presum[i-1](presum记录了前缀和)就是最小的前缀和之差,所以问题就到了怎么求最小的前缀和之差(而且还要是正数)

解法:
求出每个下标对应的前缀和,并记录下下标的具体值(结构体),然后根据前缀和从小到大排序,最小的前缀和之差一定是某两个相邻的前缀和之差(而且要保证大的前缀和对应的下标靠后),因为两个前缀和的值隔的最近,注意:要判断大于0。

代码:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll  INF = 0x3f3f3f3f3f3f3f3f;
const int PI = acos(-1);
const int E = exp(1);
const int MOD = 1e9+7;
const int MAX = 5e4+5;
int n;
ll a[MAX];

struct Node
{
    ll presum;
    int index;
};
Node pre[MAX];

int cmp(Node a,Node b)
{
    return a.presum < b.presum;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    while(cin >> n)
    {
        for(int i = 1; i <= n; i++)
        {
            cin >> a[i];
        }
        /**
		我觉得这里的初始化pre[0]和下面的排序n+1个数不好理解:
		我的理解:
		比如有一个测试数据为5.6.7.8.9,那按前缀和排完序后,下标也是1.2.3.4.5递增的,
		如果没有初始化pre[0]的话,那也就当作pre[0].index是乱值(很大的值),
		那么要求最大子段和的时候,第一次循环pre[1].index也就小于pre[0].index,
		那求出来的最大子段和就不一定是5了(实在不理解,评论我,一起讨论下)。
        */
        pre[0].presum = 0; pre[0].index = 0;
        for(int i = 1; i <= n; i++)
        {
            pre[i].presum = pre[i-1].presum + a[i];
            pre[i].index = i;
        }
        ll min_sum = INF;
        sort(pre,pre+n+1,cmp);//序列中的n个数 + index为0的数
        for(int i = 1; i <= n; i++)
        {
            if(pre[i].index > pre[i-1].index)
            {
                ll tmp = pre[i].presum - pre[i-1].presum;
                if(tmp > 0 && tmp < min_sum)
                {
                    min_sum = tmp;
                }
            }
        }
        cout << min_sum << endl;
    }
    return 0;
}

最大子矩阵和

题解:

首先我先指出这个题目的无敌巨坑点,那也就是是先输入列m再输入行n,但是给出的唯一样例正好两个数一样,这个地方搞得我dubug找了好久。
然后我一开始的想法就是直接暴力,开了六重循环(1.2层枚举起点,3.4层枚举终点,5.6层算和),当然超时了,然后就开始看题解。
我先推荐一篇写的好的博客,然后再来简要的说说我的理解。
思路
题目要求最大子矩阵和,那无非也就是找出所有的矩阵(当然枚举会超时,我已经试过了,那就想办法优化呗,其实脑子里不能有明确的目标,比如我一定要优化到几层循环,达到O(几)的复杂度,这样反而想不出来),联想到最简单的一位数组上的最大子段和,就是一个O(n)的循环遍历每个数求出来的(运用了动态规划的思想),这里就可以以行为单位,在输入数据的时候,求出前缀和,比如,a[3][3] = a[2][3] + a[1][3],开一个双层循环,枚举矩阵的上比i和下边j,最后枚举列k,说一下,很多题解又开了一个dp数组,但是根本没必要,就像我第一题没用dp一样,直接一个sum就可以了,反正只是要记录前一个值,按照**算法(类似于求一维数组最大子段和的思路)**求就完事了,比如,a[3][1] - a[1][1]就相当于是求这个矩阵的和在这里插入图片描述
a[3][2] - a[1][2]就相当于是求这个矩阵的和在这里插入图片描述
a[3][3] - a[1][3]就相当于是求这个矩阵的和在这里插入图片描述
最后实时的记录下max_sum即可。
注意点:

  • 上述说的输入顺序是先列m再行n
  • 数组a,包括计数的sum都需要设成long long型
  • max_sum需要设置为0,因为题目说了,若都是负数则输出0

代码:

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll  INF = 0x3f3f3f3f3f3f3f3f;
const int PI = acos(-1);
const int E = exp(1);
const int MOD = 1e9+7;
const int MAX = 500+5;
int m,n;
ll a[MAX][MAX];

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    while(cin >> m >> n)
    {
        memset(a,0,sizeof(a));
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= m; j++)
            {
                cin >> a[i][j];
                a[i][j] += a[i-1][j];// 以行为单位求该列的前缀和,比如a[3][3] = a[3][3]+a[2][3]+a[1][3] 
            }
        }
        ll max_sum = 0;
        for(int i = 1; i <= n; i++)
        {
            for(int j = i; j <= n; j++)
            {
                ll sum = 0;
                for(int k = 1; k <= m; k++)
                {
                	/*
                		算法的核心之处:
                		1.以列为单位(而不是以单个数),所以前面要有求前缀和
	                	2.一位数组求最大子段和的思路,也就是下面的判断语句
	                */
                    if(sum + (a[j][k] - a[i-1][k]) > a[j][k] - a[i-1][k])
                    {
                        sum += a[j][k] - a[i-1][k];
                    }
                    else
                    {
                        sum = a[j][k] - a[i-1][k];
                    }
                    if(sum > max_sum)
                    {
                        max_sum = sum;
                    }
                }
            }
        }
        cout << max_sum << endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值