题意:
给定一个数组和若干个询问,每次询问要求找到一个连续的区间,区间和的绝对值最接近 t (1<=n<=100000, n integers with absolute values <=10000 , 0<=t<=1000000000)
思路:
求个前缀和,将前缀和升序排个序,则 某个区间和的绝对值 即相当于 前缀和数组中两个数的差值,因此可以有两种考虑。
法一:尺取法
因为前缀和数组是按升序排序的,所以
1. 固定左端点,将右端点向右移动时,右端点与左端点处前缀和的差值是递增的。所以一旦该差值大于 t,那么右端点继续往右,差值肯定越来越大,离 t 越来越远,即没有必要再考虑。
2. 此时可以不动右端点,考虑将左端点向右移动一格,差值则会相应减少一些,此时再回到第一步。
3. 循环直到右端点超过 n,或是找到一个恰好与 t 相等的差值。
(右端点超过 n 即可退出循环的原因在于,若差值 >= t,则不会动右端点,而是将左端点右移;右端点超过了 n,则说明 差值 < t,此时若继续将左端点右移,那么差值肯定越来越小,离 t 越来越远,即没有必要再考虑了)
这道题一个很巧妙的地方就在于 它要求的是区间和的绝对值,所以就算将前缀和数组排序之后顺序乱了,对绝对值也是没有影响的。就比如说现在 sum[5] < sum[2], 于是 sum[5] 排在了 sum[2] 前面,那么我们算到的是 sum[2] - sum[5],它也就是 abs(a[3]+a[4]+a[5]) = -(a[3]+a[4]+a[5]).
(一开始没看到绝对值,各种判断,做得我心力交瘁......)
注意点:在前缀和数组里加一个 0 一起排序,不然就不能通过数组中已有元素两两间的差值反映 第一个元素到某个元素 的这段区间的和了。
AC Code:
#include <cstdio>
#include <algorithm>
#include <cstring>
#define maxn 100010
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
typedef long long LL;
LL a[maxn];
struct node {
LL val; int p;
}sum[maxn];
int n, k;
template<typename T>
inline T abs(T x) { return x > 0 ? x : -x; }
bool cmp(node u, node v) { return u.val < v.val; }
void work() {
memset(sum, 0, sizeof(sum));
for (int i = 1; i <= n; ++i) {
scanf("%lld", &a[i]);
sum[i].val = sum[i - 1].val + a[i];
sum[i].p = i;
}
sort(sum, sum+1+n, cmp);
while (k--) {
LL t;
scanf("%lld", &t);
int l = 0, r = 1, al, ar;
LL diff = inf, ans;
while (true) {
if (l == n + 1) break;
bool flag = false;
while (r <= n) {
LL d = sum[r].val - sum[l].val;
if (abs(t - d) < diff) {
diff = abs(t - d);
ans = d;
al = sum[l].p, ar = sum[r].p;
if (al > ar) swap(al, ar);
++al;
}
if (d == t) flag = true;
if (d >= t) break;
++r;
}
if (r == n + 1 || flag) break;
++l;
if (l == r) ++r;
}
printf("%lld %d %d\n", ans, al, ar);
}
}
int main() {
while (scanf("%d%d", &n, &k) != EOF && n + k) work();
return 0;
}
法二:lower_bound
还是基于排好序的前缀和数组,对于前缀和中的每一个元素 a[i], 找到分别最靠近 a[i] + t 与 a[i] - t 的元素,再取其中 dist 最小的那个。n 个 dist 比较一下取个最小值。
一开始想到的其实是这种办法......复杂度是 nklogn,题目没给 k 的范围,试着写了一发,竟然不是 T 而是 WA 了,就很不开心Orz.
一个麻烦的地方在于,找到的元素很可能就是 a[i] 本身,但是后来加了判断,也一直和上面那个 A 了的程序对拍,对拍也没问题,再交也还是WA...
WA的代码:(哪位好心人帮忙捉捉虫QWQ)
#include <cstdio>
#include <algorithm>
#include <cstring>
#define maxn 100010
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
typedef long long LL;
LL a[maxn];
struct node {
LL val; int p;
}sum[maxn];
int n, k;
bool cmp(node u, node v) { return u.val < v.val; }
template<typename T>
inline T abs(T x) { return x > 0 ? x : -x; }
inline LL min(LL a, LL b) { return a < b ? a : b; }
int pos(int i, LL x, LL& dif) {
node nd; nd.val = x;
int p = lower_bound(sum, sum+1+n, nd, cmp) - sum;
if (p == 0) {
if (p != i) { dif = sum[0].val - x; return 0; }
else { dif = sum[1].val - x; return 1; }
}
if (p == n + 1) {
if (p != i) { dif = x - sum[n].val; return n; }
else { dif = x - sum[n - 1].val; return n - 1; }
}
LL dif1, dif2, p1, p2;
if (p != i) dif1 = sum[p].val - x, p1 = p;
else {
if (p == n) dif1 = inf;
else dif1 = sum[p + 1].val - x, p1 = p + 1;
}
if (p - 1 != i) dif2 = x - sum[p - 1].val, p2 = p - 1;
else {
if (p >= 2) dif2 = x - sum[p - 2].val, p2 = p - 2;
else dif2 = inf;
}
if (dif1 > dif2) { dif = dif2; return p2; }
else { dif = dif1; return p1; }
}
void work() {
memset(sum, 0, sizeof(sum));
for (int i = 1; i <= n; ++i) {
scanf("%lld", &a[i]);
sum[i].val = sum[i - 1].val + a[i];
sum[i].p = i;
}
sort(sum, sum + 1 + n, cmp);
while (k--) {
LL q;
scanf("%lld", &q);
LL dif = inf, ans; int l, r;
for (int i = 0; i <= n; ++i) {
LL dif1, dif2;
int p1 = pos(i, sum[i].val + q, dif1),
p2 = pos(i, sum[i].val - q, dif2);
if (min(dif1, dif2) < dif) {
int p;
if (dif1 < dif2) { p = p1; dif = dif1; }
else { p = p2; dif = dif2; }
ans = abs(sum[p].val - sum[i].val);
l = sum[p].p, r = sum[i].p;
if (l > r) swap(l, r);
++l;
if (dif == 0) break;
}
}
printf("%lld %d %d\n", ans, l, r);
}
}
int main() {
while (scanf("%d%d", &n, &k) != EOF && n + k) work();
return 0;
}