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;
}
原文地址:https://www.cnblogs.com/fxh0707/p/14417820.html