题解:
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;
}
题解:
本题和上题的不同之处在于,数组是一个循环的,也就是会出现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;
}
题解:
刚开始我也是直接前缀和 + 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;
}