题意:给出一个整数列,求一段子序列之和最接近所给出的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<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<map>
#include<queue>
using namespace std;
#define inf 0x3f3f3f3f
#define ll long long
const int maxn=100005;
const double eps=1e-8;
const double PI = acos(-1.0);
#define p pair<ll,int>
ll myabs(ll x)
{
return x>0?x:-x;
}
int main()
{
int n,k;
while(scanf("%d%d",&n,&k))
{
int a[maxn];
p sum[maxn];
sum[0]=p(0,0);
if(!n&&!k)
break;
for(int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
sum[i]=p(a[i]+sum[i-1].first,i);
}
sort(sum,sum+n+1);
while(k--)
{
ll l=0,r=1,l1,r1,ans,t,tmp=inf;
scanf("%lld",&t);
while(r<=n)
{
ll b=sum[r].first-sum[l].first;
if(myabs(t-b)<tmp)
{
tmp=myabs(t-b);
ans=b;
l1=sum[l].second;
r1=sum[r].second;
}
if(b>t)
l++;
else if(b<t)
r++;
else
break;
if(l==r)
r++;
}
if(l1>r1)
swap(l1,r1);
printf("%lld %lld %lld\n",ans,l1+1,r1);
}
}
return 0;
}