题目链接:
http://poj.org/problem?id=3061
解题思路:
题目大意:
给定长度为n的整列整数a[0],a[1],……a[n-1],以及整数S,求出总和不小于S的连续子序列的长度的最小值。
由于所有的元素都大于零,如果子序列[s,t)满足a[s]+...+a[t-1]>=S,那么对于任何t <t'一定有a[s]+...a[t'-1]>=S。此外对于区间[s,t)上的总和来说如果令s
um[i]=a[0]+a[1]+...+a[i-1]那么a[s]+a[s-1]+...a[t-1]=sum[t]-sum[s],因此预先以O(n)的时间计算好sum的话,就可以以O(1)的时
间计算区间上的总和。这样一来,子序列的
起点s确定以后,便可以用二分搜索快速地确定使序列和不小于S的结尾t的最小值。
AC代码(二分法):
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 100005;
int n,s;
int a[maxn];
int sum[maxn+1];
void solve(){
//计算sum
for(int i = 0; i < n; i++)
sum[i+1] = sum[i]+a[i];
if(sum[n] < s){
printf("0\n");
return ;
}
int res = n;
for(int i = 0; sum[i] + s <= sum[n];i++){
//利用二分搜索求出t
int t = lower_bound(sum+i,sum+n,sum[i]+s)-sum;
res = min(res,t-i);
}
printf("%d\n",res);
}
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&s);
for(int i = 0; i < n; i++)
scanf("%d",&a[i]);
solve();
}
return 0;
}
这个时间复杂度是O(nlogn),虽然足以解决这个问题,但我们还可以更高效地解决。我们设以a[s]开始的总和最初大于S时的连续子序列为a[s]+a[s+1]+……a[t-1],这时,
a[s+1]+a[s+1]+……a[t-2]<a[s]+a[s+1]+……a[t-2]<S,所以如果从a[s+1]开始总和最初超过S的连续子序列是a[s+1]+……a[t‘-1],则t<=t'。
故可以设计如下算法:
(1)初始s=t=sum=0
(2)只要依然有sum<S,就不断将sum加上a[t],并且t=t+1;
(3)如果(2)中无法满足sum>=s则终止,否则ans=min(ans,t-s);
(4)将sum减去a[s],s增加1然后回到(2).
对于这个算法,因此t最多变化n次,因此只需O(n)的复杂度就可以求解这个问题了。
5 1 3 5 10 7 4 9 2 8
↓
5 1 3 5 10 7 4 9 2 8
↓
5 1 3 5 10 7 4 9 2 8
↓
5 1 3 5 10 7 4 9 2 8
↓
5 1 3 5 10 7 4 9 2 8
↓
5 1 3 5 10 7 4 9 2 8
↓
5 1 3 5 10 7 4 9 2 8
↓
5 1 3 5 10 7 4 9 2 8
AC代码(尺取法):
#include <iostream>
#include <cstdio>
using namespace std;
int n,S;
int a[100005];
void solve(){
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++];
}
if(res > n){
//解不存在
res = 0;
}
printf("%d\n",res);
}
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&S);
for(int i = 0; i < n; i++)
scanf("%d",&a[i]);
solve();
}
return 0;
}