题目:
Subsequence (POJ No.3061)
给定长度为n的数列整数a,*,an,以及整数S。求出总和不小于S的连续子序列的长度的最小值。如果解不存在,则输出0。
④限制条件
●10<n< 10^5
●0<aj≤10^4
●S< 10^8
输入
n=10
s=15
a = {5,1,3,5,10,7, 4, 9,2,8}
输出
2 (5+10)
输入
n=5
S=11
a= {1,2,3,4,5}
输出
3 (3+4+5)
由于所有的元素都大于零,如果子序列[s, t)满足as+..+a(t-1)≥s,那么对于任何的t<t'一定有as+...+a(t'-1)≥S。此外对于区间[s,t)上的总和来说如果令
sum(i)=a0+a1+..+a(i-1)
那么
as+a(s+1)+..+a(t-1)=sum(t)-sum(s)
因此预先以O(n)的时间计算好sum的话,就可以以O(1)的时间计算区间上的总和。这样一来,子序列的起点s确定以后,便可以用二分搜索快速地确定使序列和不小于S的结尾的最小值。
这个算法的复杂度是O(nlogn),虽然足以解决这个问题,但我们还可以更加高效地求解。我们设以as,开始总和最初大于S时的连续子序列为+..+ar-r,这时
所以从a++开始总和最初超过S的连续子序列如果是
的话,则必然有t≤t'利用这一-性
质便可以设计出如下算法:
- (1)以s=1= sum= 0初始化。
- (2)只要依然有sum<S,就不断将sum增加at,并将t增加1。
- (3)如果(2)中无法满足sum≥S则终止。否则的话,更新res = min(res, t-s)。
- (4)将sum减去as,s增加1然后回到(2)。
对于这个算法,因为最多变化n次,因此只需O(n)的复杂度就可以求解这个问题了。
实现:
#include<iostream>
#include<vector>
using namespace std;
vector<int> A;
int main() {
int n,S;
cin>>n>>S;
A.resize(n);
for(int i=0; i<n; ++i)
cin>>A[i];
int res=n+1;
int s=0,t=0,sum=0;
while(1) {
while(t<n&&sum<S)
sum+=A[t++];
if(sum<S)
break;
res=min(res,t-s);
sum-=A[s++];
}
res=(res>n?0:res);
cout<<res;
return 0;
}