http://poj.org/problem?id=2566
给出一个整数列,求一段子序列之和最接近所给出的t。输出该段子序列之和及左右端点。
【思路】
……前缀和比较神奇的想法。一般来说,我们必须要保证数列单调性,才能使用尺取法。
预处理出前i个数的前缀和,和编号i一起放入pair中,然而根据前缀和大小进行排序。由于abs(sum[i]-sum[j])=abs(sum[j]-sum[i]),可以忽视数列前缀和的前后关系。此时,sum[r]-sum[l]有单调性。
因此我们可以先比较当前sum[r]-sum[l]与t的差,并更新答案。
如果当前sum[r]-sum[l] < t,说明和还可以更大,r++。
同理,如果sum[r]-sum[l]>t,说明和还可以更小,l++。
如果sum[r]-sum[l]=t,必定是最小答案。
【注意点】
由于序列不能为空,即l<>r,如果l=r则r++。
我们更新答案的时候左右区间端点为乱序,输出的时候调整一下。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
#include <algorithm>
using namespace std;
const int INF=1<<30;
int n,k,t;
pair <int,int> sum[100005];
int main(){
while ((scanf("%d%d",&n,&k))!=EOF){
sum[0]=make_pair(0,0);
int tmp=0;
for (int i=1;i<=n;i++){
int x;
cin >> x;
tmp+=x;
sum[i]=make_pair(tmp,i);
}
sort(sum,sum+n+1);
while (k--){
cin >> t;
int Ans_l,Ans_r,Ans;
int minAns=INF;
int l=0,r=1;
while (r<=n&&minAns){
int delta=sum[r].first-sum[l].first;
if (abs(delta-t)<=minAns){
minAns=abs(delta-t);
Ans=delta;
Ans_l=sum[l].second;
Ans_r=sum[r].second;
}
if (delta<t) r++;
if (delta>t) l++;
if (l==r) r++;
}
if (Ans_l>Ans_r) swap(Ans_l,Ans_r);
cout << Ans << " " << Ans_l+1 << " " << Ans_r << endl;
}
}
}