1044 Shopping in Mars (25 分)
题意
给出一个数字序列与一个数S,在数字序列中求出所有和值为S的连续子序列(区间下标左端点小的先输出,左端点相同时右端点小的先输出)。若没有这样的序列,求出和值恰好大于S的子序列(即在所有和值大于S的子序列中和值最接近S)。假设序列下标从1开始。
思路
令Sum[i]表示A[1]到A[j]的和值,即令Sum[i] =a[1]+ a[2] +...+ a[i]。要注意序列都是正值,因此Sum[j]一定是严格单调递增的,即有Sum[1] < Sum[2] <...< Sum[n]成立(为了下面计算方面,初始化Sum[0] =0)。这样做的好处在于,如果要求连续子序列A[j到A[i]的和值,只需要计算Sum[j] - Sum[i-1]即可。
既然Sum数组严格单调递增,那就可以用二分法来做这道题。假设需要在序列A[1] ~ A[n]中寻找和值为S的连续子序列,就可以枚举左端点i(1≤i≤n),然后在Sum数组的[i, n]范围内查找值为Sum[i- 1]+S的元素(由Sum[j]-Sum[i-1]=S推得)是否存在:如果存在,则把对应的下标作为右端点j;如果不存在,找到第一个使和值超过S的右端点j。可以直接按照lower_bound 函数或者upper_bound函数的写法来解决这个问题。但是要注意,在使用upper_bound时求得的位置并不一定是恰好是右端点的位置(可能需要减1)。
const int N=1e5+10;
int a[N];
int sum[N];
int n,m;
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum[i]=sum[i-1]+a[i];
}
bool ok=false;
int l=1,r=n;
vector<PII> res;
for(int i=1;i<=n;i++)
{
int j=lower_bound(sum+i,sum+n+1,m+sum[i-1])-sum;
if(j > n) sum[j]=INF;
if(sum[j]-sum[i-1] == m)
{
ok=true;
cout<<i<<'-'<<j<<endl;
}
if(sum[j]-sum[i-1] < sum[r]-sum[l-1])
{
res.clear();
l=i,r=j;
res.pb({i,j});
}
else if(sum[j]-sum[i-1] == sum[r]-sum[l-1])
{
res.pb({i,j});
}
}
if(!ok)
for(int i=0;i<res.size();i++)
cout<<res[i].fi<<'-'<<res[i].se<<endl;
//system("pause");
return 0;
}