题目大意
给定一组包含n个整数的数列和k个询问,求取一个子串,使得该***连续子串***的***和的绝对值***最接近t
注:暴力求解必定超时
解题思路
- 由于要求取一个连续子串的部分和(和为非单调数列,若是单调数列,可不求和,直接算),这让我们想到了记录前缀和sum的方式来在O(1)内求得任意子串的和。
- 对于子串问题的处理,为了避免暴力双重循环,我们往往使用尺取法(双指针法),就像是在字符串匹配问题时的那样。当然,其他的方法(如DP)也经常用来处理子串问题,最经典的有最长上升子序列。
- 在使用双指针法时,要求序列具有一定的单调性。也正是因为序列具有单调性,才是我们能固定左端点,移动右端点,到达一定情况时,则可以移动左端点,从未有过任何回溯,从而形成节约。
- 这里我们显然不能将原序列sort后进行取尺操作,这会丧失原序列的位置信息,不满足子串条件。可借用pair记录位置信息。
- 在双指针移动中,可能导致st、en重合,这是需要人为地将en向后移动。当重合时,sum相减为0,相当于不存在数。
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string.h>
#include <cmath>
#define INF 0x3f3f3f3f
#define MAXN 100005
using namespace std;
typedef pair <int, int> p;
int n, k, t;
int a[MAXN];
p sum[MAXN];
int main()
{
while(scanf("%d%d", &n, &k), n+k){
sum[0] = p(0, 0);
for(int i = 1; i <= n; i++){
scanf("%d", &a[i]);
sum[i] = p(sum[i-1].first + a[i], i);
}
sort(sum, sum + 1 + n);//从0开始,否则无法记录从a[1]开始的序列
while(k--){
scanf("%d", &t);
int st = 0, en = 1, tmp = INF, b, l, u, ans;
while(en <= n){
b = sum[en].first - sum[st].first;
if(abs(t - b) < tmp){
tmp = abs(t - b);
ans = b;
l = sum[st].second;
u = sum[en].second;
}
if(b < t) en++;//此题先处理,再移右端点,且一个个移
else if(b > t) st++;
else break;
if(st == en) en++;//无数字存在的情况
}
if(l > u) swap(l, u);
printf("%d %d %d\n", ans, l + 1, u);//序列中不包含a[l]
}
}
return 0;
}