最小正子段和
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;
}