您的位置:首页 > 理论基础 > 数据结构算法

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

2014-08-17 18:38 253 查看
如果是线段树来做这个操作,很明显一个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也是一个可以用此方法使用树状数组的题目。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息