以前经常听说树套树很难写,今天写了一下感觉挺简单的啊。
最起码是1A233.
3196: Tyvj 1730 二逼平衡树
Description
您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:
1.查询k在区间内的排名
2.查询区间内排名为k的值
3.修改某一位值上的数值
4.查询k在区间内的前驱(前驱定义为小于x,且最大的数)
5.查询k在区间内的后继(后继定义为大于x,且最小的数)
Input
第一行两个数 n,m 表示长度为n的有序序列和m个操作
第二行有n个数,表示有序序列
下面有m行,opt表示操作标号
若opt=1 则为操作1,之后有三个数l,r,k 表示查询k在区间[l,r]的排名
若opt=2 则为操作2,之后有三个数l,r,k 表示查询区间[l,r]内排名为k的数
若opt=3 则为操作3,之后有两个数pos,k 表示将pos位置的数修改为k
若opt=4 则为操作4,之后有三个数l,r,k 表示查询区间[l,r]内k的前驱
若opt=5 则为操作5,之后有三个数l,r,k 表示查询区间[l,r]内k的后继
Output
对于操作1,2,4,5各输出一行,表示查询结果
Sample Input
9 6
4 2 2 1 9 4 0 1 1
2 1 4 3
3 4 10
2 1 4 3
1 2 5 9
4 3 9 5
5 2 8 5
Sample Output
2
4
3
4
9
HINT
1.n和m的数据范围:n,m<=50000
2.序列中每个数的数据范围:[0,1e8]
3.虽然原题没有,但事实上5操作的k可能为负数
题解
这道题暂时学习的是经典的线段树套平衡树(Treap)的做法。
重在理解和认识这个数据结构。
我们对于线段树的每一个区间,用一颗Treap来维护,由于每个节点只在线段树的$\Theta(logn)$个区间里,所以平衡树最多也只需要维护$\Theta(nlogn)$,我一开始听说这种东西还不明白这到底是怎么搞得,看了看别人的实现发现非常简单。HPD也没有每条链单独开一颗线段树而是放在一颗线段树里处理的啊。
同样我们用一片Treap森林维护每个区间就可以啦。
由于每次查询只会分成$\Theta(logn)$个区间,每个区间用Treap做到$O(logn)$处理即可。
所以好像能用树套树维护的最重要一点和线段树一样依旧是满足可合并性。。这样一来就能想到根号算法的适用性之广了。。
具体来说:
操作一:查询区间内数的排名
满足子区间可加性,及查每个小区间有多少个比当前数小的,加起来+1即为排名
将查询区间分成$O(logn)$个区间,每个区间用Treap查询多少个比当前数小的(和查排名略有不同)
时间复杂度$O(log^2n)$
操作二:查询区间第k大
由于这个不满足区间可加性,因此不能直接利用树套树完成。
但是我们知道答案随k增大而增大,因此二分权值查询的排名有单调性。可以二分查找转化为第一问。
时间复杂度$O(log^3n)$
巨慢无比。。。
操作三:单点修改。
转化成删除原数加入新数。
由于每个元素最多在$O(logn)$个线段树区间内,每个区间的平衡树可以在$O(logn)$的时间里完成插入删除,所以时间复杂度是$O(log^2n)$
操作四和五
均满足子区间答案的可合并性质,因此对$O(logn)$个区间$O(logn)$查询前驱后继再分别取最大最小即可。
时间复杂度$O(log^2n)$
这样我们就完美的解决了这道问题。
当然200行代码才是最重要的233
Code:
1 |
|
不过就是写的时间有点长,毕竟谨慎,用了将近三个小时写完qvq。
看看NOI组别还有什么是需要提前学习学习的qwq。