首页 > 代码库 > POJ 3061 (二分+前缀和)

POJ 3061 (二分+前缀和)

题目链接: http://poj.org/problem?id=3061

题目大意:找到最短的序列长度,使得序列元素和大于S。

解题思路

两种思路。

一种是二分+前缀和。复杂度O(nlogn)。有点慢。

二分枚举序列长度,如果可行,向左找小的,否则向右找大的。

前缀和预处理之后,可以O(1)内求和。

#include "cstdio"#include "cstring"int sum[100005],n,s,a,T;bool check(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]>=s) return true;    }    return false;}int main(){    //freopen("in.txt","r",stdin);    scanf("%d",&T);    while(T--)    {        scanf("%d%d",&n,&s);        for(int i=1;i<=n;i++)        {            scanf("%d",&a);            sum[i]=sum[i-1]+a;        }        int l=0,r=n,ans=0;        while(l<=r)        {            int mid=l+(r-l)/2;            if(check(mid)) {ans=mid;r=mid-1;}            else l=mid+1;        }        printf("%d\n",ans);        memset(sum,0,sizeof(sum));    }}
二分法

另一种是某本著名的日译ACM书介绍的尺取法。复杂度O(n)

这个方法很简单。

①令L=1,先找一下满足要求的第一个长度(当然不一定是最优结果)。期间R++不停伸展。

②满足了是吧,现在踢掉第一个元素,令L++。从第二个元素看起,不符合要求继续伸展R。更新一下ans。继续踢第二个元素。

③踢踢踢,直到不能伸展R,且不符合要求,break。

这种方法只有一个疑问点,就是R不往回移动,其结果一定是对的吗?

考虑一下,L一直向右移动,R其实没必要向左动了。R只有在不满足条件的时候才向右,否则停在原位。

此时凭L的移动已经能找出所有可行的区间了。可以联想一下滑动变阻器,固定R,滑动L。

 

#include "cstdio"#include "cstring"#include "iostream"using namespace std;int a[100005],n,s,l,r,T,sum,ans;int main(){    //freopen("in.txt","r",stdin);    scanf("%d",&T);    while(T--)    {        scanf("%d%d",&n,&s);        for(int i=1;i<=n;i++) scanf("%d",&a[i]);        l=1,r=1,sum=0,ans=0x3f3f3f3f;        while(true)        {            while(sum<s&&r<=n) sum+=a[r++];            if(sum<s) break;            ans=min(ans,r-l);            sum-=a[l++];        }        if(ans==0x3f3f3f3f) printf("0\n");        else printf("%d\n",ans);    }}

 

13592296neopenx3061Accepted548K79MSC++593B2014-11-02 20:53:32

POJ 3061 (二分+前缀和)