尺取法(双指针法)-POJ2566

题目大意

给定一组包含n个整数的数列和k个询问,求取一个子串,使得该***连续子串***的***和的绝对值***最接近t

注:暴力求解必定超时

解题思路

  1. 由于要求取一个连续子串的部分和,这让我们想到了记录前缀和sum的方式来在O(1)内求得任意子串的和。
  2. 对于子串问题的处理,为了避免暴力双重循环,我们往往使用尺取法(双指针法),就像是在字符串匹配问题时的那样。当然,其他的方法(如DP)也经常用来处理子串问题,最经典的有最长上升子序列
  3. 在使用双指针法时,要求序列具有一定的单调性。也正是因为序列具有单调性,才是我们能固定左端点,移动右端点,到达一定情况时,则可以移动左端点,从未有过任何回溯,从而形成节约。
  4. 这里我们显然不能将原序列sort后进行取尺操作,这会丧失原序列的位置信息,不满足子串条件。
  5. 结合前缀和,我们可以用结构体Node记录前缀和sum和位置idx,然后进行sort,然后对其进行取尺操作。结构体数组sum保证了递增性,我们可以不重复地尽可能地获取到最接近t的元素和。

坑点

  1. 由于sum单调递增,所以s=sum[j].s-sum[i].s求得的必为正数,由于sort后idx被打乱,故事实上可能是前减后、也可能是后减前,综合来看s就是子串部分和的绝对值
  2. 部分和sum[j].s-sum[i].s对应的是原始索引l=sum[j].ir=sum[i].i,不是j和i。
  3. 部分和sum[j].s-sum[i].s是原始序列a[i+1]+…+a[j],注意起点是i+1
  4. 在双指针移动中,可能导致i、j重合,这是需要人为地将j向后移动。注意,鉴于这一点,循环判断条件要写为i<=j && j<=n 而非i<j && j<=n
  5. 当部分和s等于t时,此时已最接近t,循环可以直接退出。当然,程序中可能有多个s等于t的时候,但题目中对此没有要求,我们简单地处理为“一旦遇到这种情况就 结束循环”。

AC代码

#include <iostream>
#include <string>
#include <algorithm>
#include <cmath>
#include <cstdio>

using namespace std;


struct Node
{
    int s;
    int i;

    friend bool operator<(const Node & a,const Node & b){return a.s<b.s;}
};

const int maxn=1e5+10;
const int INF=1e9+10;

int n,k;
Node sum[maxn];

int main()
{
    while(~scanf("%d %d",&n,&k) && n && k)
    {
        sum[0].s=0,sum[0].i=0;
        for(int i=1;i<=n;i++)
        {
            int a;scanf("%d",&a);
            sum[i].s=sum[i-1].s+a;
            sum[i].i=i;
        }
        sort(sum,sum+n+1);
        while(k--)
        {
            int t;scanf("%d",&t);
            int total,min_dif=INF;
            int l,r;
            int i=0,j=1;
            while(i<=j && j<=n)
            {
                int s=sum[j].s-sum[i].s;//以i为起点的序列和,随j增大而增大!!
                int dif=abs(s-t);
                if(dif<min_dif)
                {
                    min_dif=dif,l=sum[i].i,r=sum[j].i,total=s;//注意l、r是原序列的索引!!!不是这里的i、j!
                }
                if(s<t)  j++;//还可以更接近
                else if(s>t)  i++;//已经不可能更接近了
                else  break;//已经一致了
                if(i==j)  j++;//用于避免i、j重合
            }
            if(l>r) swap(l,r);
            printf("%d %d %d\n",total,l+1,r);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值