树状数组快速寻找getsum(i) < k的最大的 i

12 篇文章 0 订阅
11 篇文章 0 订阅

如果是线段树来做这个操作,很明显一个query下去就可以,复杂度O(logn);

而如果是树状数组来做的话,很容易想到的就是二分查找了,如下

int find(int sum) {
    int l = 1, r = MAX_N, m, ans = 0;
    while(l <= r) {
        m = (l+r)>>1;
        if(getsum(m) < sum) ans = m, l = m+1;
        else r = m-1;
    }
    return ans;
}

二分的复杂度是O(logn),getsum的复杂度O(logn)。两个一叠起来,肯定是打不过线段树的O(logn)。那么有没有办法降低复杂度,不那么频繁的调用getsum呢?

不要忘了树状数组的英文名是Binary Indexed Tree,二进制索引树,我们可以考虑从二进制入手分析。

我们先来看getsum函数,假设参数x是90,其二进制表示为1011010。getsum的求和过程是bit[1011010] + bit[1011000] + bit[1010000] + bit[1000000](未方便说明,[]使用的是二进制);

如果把这个过程反过来,改为bit[1000000] + bit[1010000] + bit[1011000] + bit[1011010],根据树状数组的性质。我们有bit[1<<i] > bit[1011010](when i > 6)(这个很容易看出来)。这个说明我们从高位到低位枚举二进制位数的时候,可以很容易的确定所要找的数的第一个1的位置。

那么第二1的位置呢?这时由于bit[1<<6 | 1<<i](i<6)代表的数是从(1<<6)+1~1<<6|1<<i的数的和。所以仍然有bit[1<<6|1<<i] < bit[1<<6|1<<j] (i<j)。说明我们在枚举的过程中也可以确定第二个1的位置。

剩余的1分析同理。也就是说,我们可以在一个从高位到低位枚举二进制位数的过程中找到我们所要的答案。这个情况下,每个位置都只要做一个if判断和几个加减法,复杂度O(logn),这样我们就有了能打过线段树的写法。如下

int getPos(int sumToFind) {
    int pos = 0, nowSum = 0, k = 0;
    while(1<<k < MAX_N)
        k++;
    for(int i = k; i >= 0; i--) {
        pos += 1<<i;
        if(pos > MAX_N || nowSum + bit[pos] >= sumToFind) 
            pos -= 1<<i;
        else nowSum += bit[pos];
    }
    return pos;
}

和这个有关的题目有POJ 2828,这个题用正常的二分查找应该是可以过的,然后BUPT OJ 494(BUPT oj需要登录才可以看见题)与poj的题题意相同,但是数据较强,正常的二分查找会TLE,要加读入优化等才可以擦边过。SGU 311也是一个可以用此方法使用树状数组的题目。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值