权值线段树 学习笔记
权值线段树可以用来代替平衡树的一些操作,如果不知道平衡树是干什么用的,请先学习平衡树。
首先,我们想一下线段树可以干什么??
区间加,单点加,区间和……其实还有很多操作,只不过权值线段树一般只用到这几个比较常见的操作。
权值线段树的实质是对值域建线段树,权值线段树对值域中的每一个值都建了一个叶子节点,所以建树时间复杂度是 \(\Theta (n\log_2 [l,r])\) , \([l,r]\) 是值域。单点修改,插入就是 \(\log_2[l,r]\) 。
考虑平衡树的添加一个数,因为值域中的每一个值都对应一个叶子,所以我们直接找到这个叶子节点,将它的数量加 \(1\) 就可以了,删除一个数同理。
求第 \(k\) 大和平衡树差不多,如果 \(k\) 小于等于左儿子就递归左子树,大于就递归右儿子,同时 \(k\) 减去左儿子的大小。
求 \(x\) 为第 \(k\) 大也和平衡树差不多,如果 \(x\le mid\) 就递归左子树,如果 \(x>mid\) 就递归右子树,同时 \(k\) 加右子树的大小。
因为值域一般比较大,而空间复杂度是 \(\Theta([l,r])\) ,很多题目会炸掉,所以一般采用两种方法来优化。
第一种方法是离散化,将所有操作的数据离散化为 \([1,n]\) 然后在 \([1,n]\) 建树,每次操作实际修改离散化后的值,只适合可以离线的题目,但是这种方法无论是时间复杂度还是空间复杂度都优于第二种,而且写法简单。建树 \(\Theta (n\log_2n)\) , \(n\) 是数据范围,单次操作时间复杂度为 \(\Theta (\log_2n)\) ,空间复杂度为 \(\Theta (n)\)。
第二种方法是动态开点线段树,这种方法虽然在很多方面不如第一种,但是可以在线。考虑在值域中二分每次要操作的值,只需要 \(\log_2[l,r]\) 的时间复杂度,所以我们考虑每次把二分用到的点在线段树上现用现开,这样就可以实现在线操作。所以建树时间复杂度为 \(\Theta (n\log_2[l,r])\) ,单次操作为 \(\Theta (\log_2 [l,r])\) ,空间复杂度为 \(\Theta (n\log_2[l,r])\) 。
但是需要注意的是权值线段树只能维护正数,不能维护负数,所以在处理负数的时候要统一加一个基数。
几道例题。
\(luogu\ P1168\)中位数
直接离线权值线段树,然后不断加数在 \(i\) 为奇数的时候维护第 \(i/2+1\) 大就可以了。
代码
\(luogu\ P2234 [HNOI2002]\)营业额统计
离线或者在线每次差插入的数是第 \(k\) 大,然后再查 \(k-1\) 小或者 \(k+1\) 小在减一下,取个 \(\min\) 就可以了。
代码
\(luogu\ P1486 [NOI2004]\)郁闷的出纳员
维护一个全局 \(tag\) 在每次全局加减的时候对 \(tag\) 进行操作,然后在删除的时候对 \(min\) 进行一个 \(tag\) 的逆运算,把小于等于逆运算后的数区间删除,因为加入权值线段树的值是不变的,但是 \(tag\) 是不断变化的,所以我们对当前的操作的数进行一个 \(tag\) 的逆运算,就是权值线段树中要操作的数字。插入也要进行一个 \(tag\) 的,然后每次直接查第 \(k\) 大就可以了。
需要注意的是因为进行 \(tag\) 逆运算可能有负数,所有这道题目需要统计加一个基数,然后输出的时候再减掉基数。
代码