题意
$ T $ 组数据,每组数据给一个长度 $ N $ 的序列,要求一段连续的子序列的和大于 $ S $,问子序列最小长度为多少。
输入样例
2
10 15
5 1 3 5 10 7 4 9 2 8
5 11
1 2 3 4 5
输出样例
2
3
解析
我们很容易发现对于这题我们可以二分答案,先找出一个初始长度,判断是否存在合法序列,如果存在缩小长度,如果不存在加长长度。
时间复杂度 $ O(nlogn) $
代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
int T,n,m,k,sum[100010];
bool cheak(int x){
int l,r;
for(int i=1;i+x-1<=n;++i){
l=i;r=i+x-1;
if(sum[r]-sum[l-1]>=m) return true;
}
return false;
}
int main(){
scanf("%d",&T);
while(T--){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%d",&k);
sum[i]=sum[i-1]+k;
}
int l=1,r=n,ans=0;
while(l<=r){
int mid=(l+r)>>1;
if(cheak(mid)){ans=mid;r=mid-1;}
else l=mid+1;
}
printf("%d
",ans);
}
return 0;
}
对于这一题我们还有一种其它的做法,尺取法。
1.先固定左端点 $ l=1 $,右端点一直向右走,直到满足总和 $ data>=S $ 。
2.我们可以尝试删去左端点即 $ l $ 向右移动,如果发现任然满足 $ data>=S $ 更行答案,不满足右端点向右走。
3.当右端点到达 $ N $ 时不再移动右端点,一直移动左端点直到不满足 $ data>=S $ 就 $ break $。
对于其正确性这里不做证明(我菜爆了不会
可以保证的是这种做法会扫过所以可能出现答案的区间。
时间复杂度 $ O(n) $
代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
int T,n,m,ans,a[100010];
int main(){
scanf("%d",&T);
while(T--){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
}
int data=0,l=1,r=1;ans=1<<30;
while(true){
while(data<m&&r<=n) data+=a[r++];
if(data<m) break;
ans=min(ans,r-l);
data-=a[l++];
}
if(ans==1<<30) printf("0
");
else printf("%d
",ans);
}
return 0;
}
值得一提的是,在poj上两种方法的时间相差不大,有可能是数据太小了吧。