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