最小正子段和

最小正子段和

N个整数组成的序列a1,a2,a3,…,an,从中选出一个子段(ai,ai+1,…aj),使这个子段的和>0,并且这个和是所有和>0的子段中最小的。例如:4,-1,5,-2,-1,2,6,-2。-1,5,-2,-1,序列和为1,是最小的。

Input
第1行:整数序列的长度N(2 <= N <= 50000) 第2 - N+1行:N个整数
Output
输出最小正子段和。
Sample Input

8
4
-1
5
-2
-1
2
6
-2

Sample Output

1

题目大意

负数和正数都有的一个序列,求最小子段和

思路分析

一开始想用动规做但是发现需要用到的前面的状态不止一个,因为正负都有,每一段在加上后面的数字以后都会有成为答案的机会,所以想着想着果断放弃了
所以考虑用前缀和,要是直接都跑一遍显然会炸,那肯定就需要优化一下:

敲黑板

  • 首先用到前缀和数组,既然要求正子段和,就需要保证右端点的前缀和大于左端点的值
  • 将前缀和数组建立成一个结构体,记录前缀和的值和尾端元素的位置,这样就可保证右端点的前缀和大于左端点的值(但是需要用尾端位置进行一下判断,保证一个大的那一个在右端点),然后更新答案就好说了

代码

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<cmath>
#include<algorithm>
const int N = 5e5+10;
typedef long long ll;
using namespace std;
struct point{
    ll w; //w记录大小
    int v; //v记录位置
}sum[N];
int a[N];
ll ans;
bool cmp(point x,point y){
    return x.w<y.w;
}
int main(){
    int n;scanf("%d",&n);
    for (int i=1;i<=n;i++)scanf("%d",&a[i]);
    sum[1].w=a[1];
    for (int i=2;i<=n;i++){
        sum[i].w=sum[i-1].w+a[i];
        sum[i].v=i;
    }
    sort(sum+1,sum+1+n,cmp); //从小到大排序
    if (sum[1].w>0)  ans=sum[1].w; //如果最小的前缀和数组是正的,那它就有机会成为最小正子段和
    else ans=0x3f3f3f3f3f3f3f3f;
    for (int i=2;i<=n;i++){
        if (sum[i].w>0&&sum[i].w<ans)ans=sum[i].w; //还是直接用前缀和这一整段去更新
        if (sum[i].w>sum[i-1].w&&sum[i].v>sum[i-1].v&&(sum[i].w-sum[i-1].w<ans)) //对于不是从头开始的一段,遍历每两个相邻前缀和数组的差,就是它们中间一段的值,同时保证大的在右端
          ans=sum[i].w-sum[i-1].w;
    }
    printf("%lld",ans);
    return 0;
}
原文地址:https://www.cnblogs.com/hhhhalo/p/13086469.html