【OfferX】二分索引树、线段树和矩形

1.二进制索引树(树状数组实现)

二进制性质:x&(-x)表示单独提取x的最低位1
性质1:任何一个节点到达父级节点的路径只有一条。这保证了当一个节点更新时,不会更新父节点两次。
性质2:一个节点往上可以遍历所有父节点,因此更新的时候可以保证更新所有受影响的点;但一个节点往下遍历则会跳过它的所有子节点,而只经过所有比它小但是不是其子节点的兄弟节点。也就是说,节点在往下遍历的过程中只遍历那些兄弟节点,兄弟节点的兄弟节点…这个性质是由于每次均取最低位得到的,我们可以注意到,如果j= i - (i&-i) , k=j+(j&-j), 必然有i!=k。 注意查询是向下遍历,而更新是向上遍历。
性质3:数组C[i]存储的是所有构成i的2次幂的元素的和,如果 i = (1<<k1) + (1<<k2) + … , 则C[i]=f(A[k1],A[k2],…)

图示

点更新操作

# 相当于A[i] = A[i] + e
update(i,e):
    while i<=n:
        c[i] = f(e,c[i])
        i+=i&(-i)

点查询操作

query(i):
    res=0
    while i>0:
        res = f(res,c[i])
        i-=i&(-i)
    return res

值查询操作
所谓值查询,是指给定函数f的计算结果,求出哪些点包含这个值
我们使用二分查找,每次在idx的基础查询所有的中间区间
比如, 假设有8个元素,最大值是1000, 则取二分m=1000, 然后如果遇到tree[m]<val,则应当往m较大的方向查询,因此更新idx=m,同时查询的值应当减小tree[m],因为在m=idx+bitMask的过程中,会将所有小于m的值包含进来,由于bitMask每次只增加一位,所以m必然会比较所有的区间。
最后,如果val剩下0,则idx停留在相等的下标处。

# 和read相反,查找某个等于val的下标
# 要求sum是递增的,也就是说i>j, sum[i]>sum[j]
find(val):
    idx = 0
    bitMask = highestBitOf(n)
    while bitMask != 0:
        # middle
        m = idx + bitMask
        bitMask >>= 1
        if m>n:
            continue
        if tree[m]==val:
            return m
        else if tree[m]<val:
            idx = m
            val -= tree[m]
    if val!=0:
        return -1
    return idx

2维数组的索引树

只需要将2维数组看成是两个数组,外层数组的元素是内层的数组,外层数组共有n项;每个内层数组有m个元素。
然后分别针对内外层数组做二进制索引即可

点更新操作

# 相当于A[i] = A[i] + e
update(r,p,e):
    for i = r to n:
        for j = p to m:
            c[i][j] = f(e,c[i][j])
            j+=j&(-j)
        i+=i&(-i)

Reversed Pairs

题目493. Reverse Pairs(Hard)

给定数据A, 求满足i<j 且 A[i]>2*A[j]的对数

:其实问题就转化为对每一个A[i], 求 A[0~(i-1)]中大于2*A[i]的数量,但是大于2*A[i]的数量不易使用BIT进行存储,所以转化为大于等于2*A[i]+1的数量

我们可以构造一颗BIT数, query(i)返回所有大于等于A[i]元素的数量。为了能够将元素的下标与元素的相对大小建立顺序,我们对数组进行离散化,将数组复制之后进行排序,然后我们知道每个元素的相对顺序 o(A[i]) = lower_bound(A[i] in 排序数组), 这种情况下o(A[i])对每个元素都能返回1~N内的一个下标代表其相对顺序,o(A[i]) >= o(A[j]) 与 A[i]>=A[j]等价。

如果两个元素相等,则o(A[i])总是应当返回最小的那个。

注意o(A[i])需要使用二分查找实现。

然后我们顺序遍历原来的数组来查找A[0~(i-1)]中大于等于2*A[i]+1的数量,其实查找元素此时等价于对数组下标的查找,查找大于等于o(2*A[i]+1)的数量。

使用BIT刚好满足这种查找需求,因为BIT本身的定义就是f(i)=小于等于i的所有元素的sum。

注意下面的更新操作:update, 它意味着,当k的数量增加时,所有小于k的下标,它们的指标也相应的增加了,所以更新时是向下更新。我们发现,sum[0,i]具有相反的特性,也就是说,A[i]的增长带来的影响是向上的;而这个问题中A[i]的增长带来的变化是向下的,因此更新和query操作与sum问题相反。

class Solution {
    
    int[] tree;
    int n;
    
    void update(int i,int d){
        while(i>0){
            tree[i]+=d;
            i-=i&(-i);
        }
    }
    int query(int i){
        int res=0;
        while(i<=n){
            res += tree[i];
            i+=i&(-i);
        }
        return res;
    }
    
    public int reversePairs(int[] nums) {
        n=nums.length;
        tree = new int[n+1];
        
        int[] enumerated = Arrays.copyOf(nums,n);
        Arrays.sort(enumerated);
        int cnt = 0;
        for(int i=0;i<n;++i){
            int idx = lowestIndex(enumerated,(long)2*nums[i]+1)+1;
            if(idx<=n){
                cnt += query(idx);   
            }
            idx = lowestIndex(enumerated,nums[i])+ 1;
            update(idx,1);
        }
        return cnt;
    }
    
    // argmin(target >= e)
    // all target < e,return n
    int lowestIndex(int[] nums,long e){
        // possible 
        int r=0,p=nums.length;
        while(r<p){
            int m = r + (p-r)/2;
            if((long)nums[m]>=e) p=m;
            else r=m+1;
        }
        return r;
    }
}

参考

TopCoder - BIT Tutorial

2.RMP

参考

TopCoder - RMQ

3.线段相交

判断线段是否相交的依据,给线段[ (px,py), (rx,ry)], [(qx,qy), (sx,sy)] 只需要判断点的方向即可。


           .c
  .a    .      .b
    .d

上图中ab和cd相交的必要条件就是 d->c->a是逆时针,而d->c->b是逆时针

下面的方法返回3个点的旋转方法, 只需要判断dc的斜率是ca的斜率即可

def direction(r,p,q):
    int slopeDiff = (p.y - r.y)*(q.x-p.x) - (p.x-r.x)*(q.y-p.y)
    return slopeDiff==0?"COLINEAR":(slopeDiff>0?"CLOCKWISE":"COUNTERCLOCKWISE");
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值