Description
Input
Output
Sample Input
5 1 -10 -5 0 5 10 3 10 2 -9 8 -7 6 -5 4 -3 2 -1 0 5 11 15 2 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 15 100 0 0
Sample Output
5 4 4 5 2 8 9 1 1 15 1 15 15 1 15
Source
/*
这是一道尺取法的好题, 是深刻反映尺取思想的. 并且边界条件比较严格, 很考验编码能力.
尺取法是一种技巧, 主要运用在区间范围满足某种性质的问题.
如果一道题是满足尺取法的, 那么它是具有某种单调性的.
比如说 poj3061 Subsequence
这道题是求一段连续的区间和是大于等于S的, 最小的区间长度,
因为所有数字都是正数, 所以当用lo, hi表示一段区间时,
当hi向后移动时, 区间和是上升的,
当lo向后移动时, 区间和是下降的.
也就是说, 当向后移动lo时, 区间和下降, 为了使区间和上升, 只能向后移动hi.
这就是一个单调性.
再比如说 poj3320 Jessica's Reading Problem
这道题是求一个最小的区间长度, 这个区间有Jessica要复习的所有知识.
用lo, hi表示区间, 初始都为0, 区间为空,
首先要不断递增hi, 使[lo, hi)这段区间覆盖了所有的知识点.
接着递增lo, 直到[lo, hi)这段区间首次没有覆盖所有的知识点.
这时候, 只有把hi递增才能使[lo, hi)覆盖所有的知识点.
这样不断的递增lo, hi, 也满足尺取法的单调性.
具体做法可以通过离散化 + 数组模拟
而找到Bound Found和上述两题是不一样的, 这道题初看是找不到单调性的, 必须自己构造.
这道题要求的是一段区间, 其区间和的绝对值是最接近t的.
由于数字是有正有负的,
如果用lo, hi表示一段区间的话, 递增lo, 不一定会使区间和下降,
递增hi, 不一定会使区间和上升. 这不满足尺取法要求的单调性.
如何做呢?
一段区间的和, 我们通常可以用前缀和表示.
即[lo, hi] = prefix(hi) - prefix(lo - 1)
这道题要求的是一段连续的区间, 而任意来两个前缀和相减是可以表示一段连续区间的和的.
这样我们可以通过对前缀和数组排序, 这样这个数组就是单调的了.
用lo, hi表示两个数组索引, 并且 prefix[hi] - prefix[lo]确实是一段区间的和的绝对值
递增hi, 区间和的绝对值上升,
递增lo, 区间和的绝对值下降.
这样就找到了尺取法所要求的单调性了.
关键之处就是对前缀和数组排序.
并且细节要求很苛刻, 需要细细体会
*/
#include
#include
#include
#include
using namespace std;
const int MAXN = 1e5 + 600;
const int infinite = 0x3f3f3f3f;
int a[MAXN];
pair prefix[MAXN];
int n, k, t;
int ans, l, r;
void compare(int lo, int hi){
int sum = lo == hi? -infinite: prefix[hi].first - prefix[lo].first;
if(abs(sum - t) < abs(ans - t)){
ans = sum;
l = lo;
r = hi;
}
}
int main(){
while(scanf("%d%d", &n, &k) && n + k){
prefix[0] = make_pair(0, 0);// 如果不加0, 是无法用两个索引表示出一段区间的
for(int i = 0; i < n; ++i){
scanf("%d", a + i);
prefix[i + 1] = make_pair(prefix[i].first + a[i], i + 1);// prefix[i]表示这是a数组前i个数字的和
}
sort(prefix, prefix + n + 1);
while(k--){
scanf("%d", &t);
int lo = 0, hi = 0;
ans = -infinite;
while(true){
while((hi < n) && (lo == hi? -infinite: prefix[hi].first - prefix[lo].first) < t){
++hi;
}// 递增hi, 使这段区间和的绝对值首次大于 t
if((lo == hi? -infinite: prefix[hi].first - prefix[lo].first) < t){
compare(lo, hi);
break;
}// 如果区间和的绝对值是小于t的话, 就退出
compare(lo, hi - 1);// 最优解只能在 [lo, hi - 1]
compare(lo, hi);// 和 [lo, hi]之间了
while(t <= (lo == hi? -infinite: prefix[hi].first - prefix[lo].first)){
++lo;
}// 递增lo, 使区间和的绝对值首次小于 t
compare(lo - 1, hi);
compare(lo, hi);
}
l = prefix[l].second;
r = prefix[r].second;
if(r < l){
swap(r, l);
}
// Output
printf("%d %d %d\n", ans, l + 1, r);
}// 上述写法, 时间复杂度的常数较小, 但是细节处在意过多
}
return 0;
}