题目链接:http://acm.ayit.edu.cn/problem/68
尺取法:
给定长度为n的数列整数a0,a1,a2…a(n-1)以及整数S。求总和不小于S的连续子序列的长度的最小值。如果不存在,输出0.
限制条件
10<n<1e5
0<ai<=1e4
S<1e8
输入:
n=10
S=15
5 1 3 5 10 7 4 9 2 8
输出:
2
第一种方法:前缀和+二分
时间复杂度O(nlogn)
规定:sum(i)=a0+a1+…+a(i-1)
子序列的起点s确定后,可以使用二分搜索快速地确定使序列和不小于S的结尾t的最小值
代码:
int n,S;
int a[MAX_N];
void solve()
{
for(int i=0; i<n; i++)/*从0开始,下面的res=min(res,t-s)就不用进行-1的操作*/
sum[i+1]=sum[i]+a[i];
if(sum[n]<S)
printf("0\n");
return;
}
int res=n;
for(int s=0; sum[s]+S<=sum[n]; s++)
{
int t=lower_bound(sum+s,sum+n,sum[s]+S)-sum;
//以s为起点,一直到n,找到sum数组内,满足sum[i]>=sum[s]+S的i的最小值
res=min(res,t-s);
}
printf("%d\n",res);
第二种方法:尺取法
设以a(s)为开始总和最初大于S时的连续子序列为a(s)+…+a(t-1),
如果从a(s+1)开始总和最初超过S的连续子序列如果是a(s+1)+…+a(t’-1)的话,必然有t<=t’.
t最多变化n次,所以时间复杂度O(n)
代码:
void solve()
{
int res=n+1;
int s=0,t=0,sum=0;
for(;;)
{
while(t<n&&sum<S)
sum+=a[t++];
if(sum<S)
break;
res=min(res,t-s);
sum-=a[s++];
//减去首位的数后,此时sum可能
//sum>S,不执行while,往下更新最小值
//sum<S,执行while,t对应的变化
}
}
if(res>n)
res=0;
printf("%d\n",res);
此题只是尺取法的运用
代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e5+10;
const int inf=0x3f3f3f3f;
int n,m;
int sum[N],a[N];
int main()
{
scanf("%d%d",&n,&m);
for(int i=0; i<n; i++)
scanf("%d",&a[i]);
int s=0,t=0,sum=0,mi=inf,res=n,quary;
for(;;)
{
while(t<n&&sum<m)
{
sum+=a[t++];
int tab=abs(sum-m);
if(tab<mi)
{
mi=tab;
quary=t-s;
}
else if(tab==mi)
quary=min(quary,t-s);
}
int tab=abs(sum-m);
if(tab<mi)
{
mi=tab;
quary=t-s;
}
else if(tab==mi)
quary=min(quary,t-s);
if(sum<m)
break;
sum-=a[s++];
}
printf("%d\n",quary);
return 0;
}