POJ 2566 Bound Found(尺取前缀和)

题目链接
题目大意:有\(m\)次询问, 每次要求在一个长度为\(n\)的序列中找出一个区间其和的绝对值与给出的数最接近。(这题有spj,如有多解输出其一即可)。
  题目看上去像是尺取的题,但是因为序列中既有正数又有负数,所以其区间和是不具有单调性的,直接尺取显然是无从下手的。不过既然问题问的是区间还是有办法让序列具有单调性的。如果对该序列取前缀和,然后让前缀和从小到大排序,那么这个前缀和序列的差就有了单调性。假如按从小到大排序,对于前缀和\(a_i\)\(a_j\)\(i<j\)\(j\)越大\(i\)越小,其差值就越大,也就是区间和的绝对值越大。
  但是需要注意一点的是,尺取的初位置选择。假设对前缀和序列从小到大排序,对于一段负的前缀和序列来说,应该从最小的前缀和开始尺取,这样\(r\)越大,其尺取的区间和的绝对值也就越大,\(l\)越小,其尺取的区间和的绝对值也就越小,当尺取到\(0\)时,表示从第一个位置开始进行尺取到某个位置。而对于一段正的前缀和序列,应该从\(0\)开始尺取,表示从第一个位置进行尺取到某个位置。如果对负数序列也从\(0\)开始尺取的话,就会出现\(r\)越大,区间和的绝对值越小,\(l\)越小区间和的绝对值越大的情况。所以我们每次在排序之前在前缀和序列中加入一个\(0\)元素,就能让前缀和序列无论是否有负数都具有一致性。感觉好难解释啊,实在不懂就看代码意会吧。。。

const int maxn = 1e5+10;
int n, m;
struct PRE {
    int num;int val;
    bool operator < (const PRE &a) const {
        return val<a.val;
    }
} pre[maxn];
int main(void) {
    while(~scanf("%d%d", &n, &m) && (n||m)) {
        pre[0].val = pre[0].num = 0;
        for (int i = 1; i<=n; ++i) {
            scanf("%d", &pre[i].val);
            pre[i].val+=pre[i-1].val; pre[i].num = i; 
        }
        sort(pre, pre+n+1);
        while(m--) {
            int t, l = 0, r = 1, ans, ansl, ansr, minn = INF;
            scanf("%d", &t);
            while(r<=n) {
                int d = abs(pre[r].val - pre[l].val);
                if (minn >= abs(d-t)) {
                    minn = abs(d-t);
                    ans = d;
                    ansl = pre[l].num;
                    ansr = pre[r].num;
                }
                if (d<=t) ++r;
                else ++l;
                if (l==r) ++r; //避免上一步出现l==r然后minn因此更新成0的情况,前缀和求区间相减的两个前缀和必不相等,除非是空区间
            }
            if (ansl>ansr) swap(ansl, ansr);
            printf("%d %d %d\n", ans, ansl+1, ansr); 
        }
    }
    return 0;
}
posted @ 2020-04-20 19:40  shuitiangong  阅读(106)  评论(0编辑  收藏  举报