poj 2566 Bound Found 尺取法 / lower_bound

题目链接


题意:

给定一个数组和若干个询问,每次询问要求找到一个连续的区间,区间和的绝对值最接近 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;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值