题目大意
给定长度为n的数列整数a0,a1,a2…an-1以及整数S。求出总和不小于S的连续子序列的长度的最小值,如果解不存在输出0
解题思路
先介绍一下取尺法。
取尺法通常是指对数组保存一对下标(起点和重点),然后根据实际情况交替推进两个端点直到得到答案的方法。
对应于这道题目来讲,由于每个元素都大于零,如果子序列[s,t)满足as+as+1+…+at-1>=S,那么对应于任何的t’>t。一定有as+as+1+…+at’-1>=S。此外对于区间[S,t)上的总和来讲,如果令:
sum(i) = a0+a1+….+ai-1
那么:
as + s+1+…+at-1 = sum(t)-sum(s);
如果开始的时候用o(n)的时间计算出sum的话,就可以用O(1)的时间计算区间上的和,这样一来子序列的起点i确定了以后,便可以使用二分搜索快速地确定序列和不小于S的结尾t的最小值。
AC代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 2e5 + 5;
int main()
{
int t,n,s;
int sum[MAXN];
int a[MAXN];
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&s);
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
sum[i+1] = sum[i] + a[i];
}
if(sum[n]<s)
{
printf("0\n");
continue;
}
int res = n;
for(int i=1;sum[i]+s<=sum[n];i++){//放置越界
int pos = lower_bound(sum+i,sum+n,sum[i]+s)-sum;
res = min(res,pos-i);
}
printf("%d\n",res);
}
return 0;
}
上面的算法的负责度是O(nlgn)。虽然能够解决这个问题,但是我们还能使得算法更加高效。
我们设as开始总和最初大于S的连续子序列是as+…at-1,这个是时候:
as+1+…at-2<s+…at-2 < S。
所以从as+1开始总和最初超过S的连续子序列如果是as+1+…+at’-1,则必有t’>=t,利用这个性质我们就能得到设计出如下算法:
1. 以s=t=sum=0初始化
2. 只要依然有sum
优化后的代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 2e5 + 5;
int main()
{
int t,n,s;
int a[MAXN];
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&s);
for(int i=0;i<n;i++){
scanf("%d",&a[i]);
// sum[i+1] = sum[i] + a[i];
}
//核心代码
int res = n+1;
int i=0,t=0,sum=0;
for(;;)
{
while(t<n && sum<s)
sum += a[t++];
if(sum<s)
break;
res = min(res,t-i);
sum -= a[i++];
}
if(res>n)
res= 0;
printf("%d\n",res);
}
return 0;
}
像上面反复推进区间的开头和末尾,来求取满足条件的最小区间的方法称为取尺法