专栏文章
一种“用平衡树修改自己”的算法
算法·理论参与者 15已保存评论 21
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 21 条
- 当前快照
- 1 份
- 快照标识符
- @mi94mnkq
- 此快照首次捕获于
- 2025/11/22 01:21 3 个月前
- 此快照最后确认于
- 2025/11/22 01:21 3 个月前
最近在考试时发现可以用 FHQ-Treap 做一些事情,觉得很有趣,就记录下来。若有与他人重复,还请提醒。
起因是考场上遇到了这样一个问题:
有两个数列 ,满足 。从前往后对于每个位置,求出一个数对 ,其中 ,要求 是 中 最小的位置。对于两个数对排序方式是:先比较 的大小,若相同比较 的大小。要求时间复杂度为 。
容易发现,假如我们在求解 的时候,已经知道了前 个位置的 数组排名,那么我们可以用线段树简单维护每个 。难点在于如何排序。
容易发现当你塞入一个新的数对 时,比 大的数对排名会 。因此考虑可以进行区间加的 FHQ-Treap。但是难点在于如何利用平衡树自身的排名进行分裂。因为假如我们只按照每个位置上记录的排名进行排序,可能会出现懒标记暂未下放的情况;而分裂操作会破坏树结构,因此普通平衡树中的查排名操作也无法实施。
考虑不下放标记,而让节点通过向上一步一步爬的方式找标记。定义
CPPgetnum(x) 函数表示我们从 开始,一直执行向父亲跳和给 的 加上当前点的懒标记值的操作,最终得出的数就是这个位置的真实排名,代码如下:inline int getnum(int x){
int re=num(x);x=fa(x);
while(x) re+=fl(x),x=fa(x);
return re;
}
由于平衡树深度为 ,所以这一操作的时间复杂度即为 。由于 操作本身就有一个 ,所以现在 的总时间复杂度为 。代码如下:
CPPinline int getnum(int x){
int re=num(x);x=fa(x);
while(x) re+=fl(x),x=fa(x);
return re;
}
inline bool operator<(suf x,suf y){
return x.fs!=y.fs?x.fs<y.fs:getnum(x.ls)<getnum(y.ls);
}
inline void push_up(int x){
sz(x)=sz(ls(x))+sz(rs(x))+1;
}
inline void down(int x,int k){
fl(x)+=k,num(x)+=k;
}
inline void push_down(int x){
down(ls(x),fl(x)),down(rs(x),fl(x)),fl(x)=0;
}
inline void split(int x,suf v,int &a,int &b,int af,int bf){
if(!x){a=b=0;return;}push_down(x);
if(v<val(x)) split(ls(x),v,a,ls(b=x),af,x),fa(x)=bf;
else split(rs(x),v,rs(a=x),b,x,bf),fa(x)=af;push_up(x);
}
inline int merge(int x,int y){
if(!x||!y) return x|y;push_down(x),push_down(y);
if(rk(x)<rk(y)) return rs(x)=merge(rs(x),y),fa(rs(x))=x,push_up(x),x;
return ls(y)=merge(x,ls(y)),fa(ls(y))=y,push_up(y),y;
}
这样我们就可以用总时间复杂度 的做法 AC 这个问题了。由于时间复杂度非均摊,所以理论上可以做可持久化。另外,本题理论上可以将区间加改为区间反转等更复杂的区间操作。
相关推荐
评论
共 21 条评论,欢迎与作者交流。
正在加载评论...