版权声明:未经博主同意,不可转载 https://blog.csdn.net/pythonbanana/article/details/88694046
首先,这个问题并不复杂,有O(n^2)的前缀和做法,还有分治O(nlgn)的做法。
还有O(n)的做法。
O(n)的做法,目前我知道了两种。
一种就是
sum[i,j] = sj - s(i-1),先计算前缀和,然后从左往右记录当前碰到的最小前缀和的下标,因为固定sj,就要使s(i-1)最小。然后在从左往右跑一遍,即可。
这种做法需要计算前缀和,和预处理相应的下标。总共多需要两个数组。
接下来的一种做法,节约了空间。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<set>
#include<stack>
#include<queue>
#include<map>
#include<cstring>
#include<string>
#include<cmath>
using namespace std;
typedef long long LL;
#define INF 0x3f3f3f3f
#define PI acos(-1.0)
#define pii pair<int,int>
#define all(x) x.begin(),x.end()
#define mem(a,b) memset(a,b,sizeof(a))
#define per(i,a,b) for(int i = a;i <= b;++i)
#define rep(i,a,b) for(int i = a;i >= b;--i)
const int maxn = 3e5;
int n = 0,m = 0;
int a[maxn+10];
int s[maxn+10];
int sm[maxn+10];//记录从1-j中最小的s的下标i,s[j] - s[i-1]最大,固定 s[j],相当于使s[i-1]最小
//然后扫描一次数组,维护目前遇到过的最小S
//复杂度O(n)
int main(){
while(~scanf("%d",&n)){
int l = 0,r = 0;//记录所求区间的端点值
memset(s,0,sizeof(s));
per(i,1,n){
scanf("%d",&a[i]);
s[i] = s[i-1] + a[i];
}
int mins = 0,min_id = 0;
sm[1] = 0;
per(i,2,n){//录从1-i中最小的sj的下标j ,s[j] - s[i-1]最大,固定
if(s[i-1] < mins){//取不取等号,要根据实际情况来
mins = s[i-1];
sm[i] = min_id = i-1;
}else{
sm[i] = min_id;
}
}
int maxv = -INF;
per(i,1,n){
if(s[i] - s[sm[i]] > maxv){
l = sm[i] + 1;r = i;
maxv = s[i] - s[sm[i]];
}
}
printf("%d %d : %d\n",l,r,maxv);
}
return 0;
}
2,设s[i]表示以a[i]结尾的连续子序列的最大和。由于递推s[i]数组可以节约为一个变量即可。
#include<bits/stdc++.h>
#define per(i,a,b) for(int i = (a);i <= (b);++i)
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 3e5;
int a[maxn+10];
int n = 0;
void solve(){
int l = 1,r = 1;
int s = 0,maxv = -INF;
per(i,1,n){
if(s < 0){
s = a[i];
if(s > maxv){
maxv = s;
l = i,r = i;
}
}else{
s += a[i];
if(s > maxv){
maxv = s;
r = i;
}
}
}
printf("The maximum is %d\nThe internal is %d to %d\n",maxv,l,r);
}
int main(){
while(~scanf("%d",&n)){
per(i,1,n){
scanf("%d",&a[i]);
}
solve();
}
return 0;
}
第二种为什么比第一种方法节约空间。
前者不是增量算法,后面的算法通过增量递推,充分利用了每一步的计算结果。增量,这种xx,在很多方面都有很好的应用,可以是算法变得高效。