poj 3061 Subsequence 取尺法

传送门:poj 3061 Subsequence

题目大意

给定长度为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;
}

像上面反复推进区间的开头和末尾,来求取满足条件的最小区间的方法称为取尺法

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值