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
给定数据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;
}
}
参考
2.RMP
参考
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");