不进行修改操作的主席树说是多个树,不如说是多个相互交叉的树,
建立n个树每个树都含有k(k代表n中有k个不同数)个叶节点,第i个树代表以1到n所有元素 按从小到大排列建立的线段树,节点的权值是指这个节点下有多少个点(对应于这i个叶节点的值,如果第i个数(从小到大排列的第i个数)出现则值为1,不出现值为0,节点维护的是左右儿子节点的和),查询1—i这些数中的第k个数时,只需要从第一个节点往下查找,如果左节点的值是大于等于k的,那么查询左儿子,否则查询右儿子,递归直到查到某个节点。
上面是原理,但如果直接开n个树的内存是(4*n)^2肯定会爆掉,下面是如何把它们变成相互交叉的树。
对于一个目标数组a[n],现将a[n],按从小到大的顺序排列,目的的为了在建树过程中知道某个点应该放在线段树的哪个位置。
比如 2(2) 4(4) 3(3)1(1){前面是数值,括号里是应该放的位置},这样线段树的结构是提前确定的,易于插入,用root[i]表示1—i个数形成的线段树的跟节点;i与i+1这两个数相比较,他们的树只有一个叶子节点和这个叶节点到跟节点上的节点是不同的,对于数组第i+1个值,它肯定要插入到第i个树;相比较第i个树,第i+1个数在排列时对应的位置从0变成1,然后这个叶子节点到跟节点都需要更新,但其他的节点都一样,
下面用图表示:
struct Node
{
Int L,int R,sum;
}node[N (log(N)+4),root[N]];
root[i]
L1 R1
L2 R2 L3 R3
root[i+i]
l1 r1
l2 r2 l3 r3
假设n为4,插入的位置在l3,那么l1和L1是完全相同的,直接让root[i+1]节点的L值等于root[i]的L值,然后新建一个节点保存r1节点的信息。因为r3和R3完全相同,那么直接让r1的R等于R1的R;然后新建一个节点保存l3;
.......root[i]
L1 ........... R1
L2...R2..L3....R3
......root[i+i]
L1............. l1
............... l3....R3
空间结构如上图所示。
所以时间复杂度为nlog(n),空间复杂度为nlog(n);