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;
}

猜你喜欢

转载自www.cnblogs.com/shuitiangong/p/12739951.html