『学习笔记』平衡树(todo)

会一种即可。

比线段树多的功能:

  1. 区间翻转
  2. 插入/删除

\(\large\texttt{Treap}\)

Treap 即 Tree + Heap。相比二叉搜索树,每个节点多维护一个随机数 \(pri\) 作为权值,并按照 \(pri\) 维护整棵树的堆的性质。这样可以避免复杂度退化。

板子:

const int N=1e5+5,inf=0x3f3f3f3f;
class Treap{
    public:
        void insert(int &rt,int x){
            if(!rt){
                rt=++cnt;
                t[rt].size=t[rt].cnt=1;
                t[rt].val=x;
                t[rt].pri=rand();
                return;
            }
            if(t[rt].val==x){
                t[rt].cnt++,t[rt].size++;
                return;
            }
            int k=x>t[rt].val;
            insert(t[rt].s[k],x);
            if(t[rt].pri<t[t[rt].s[k]].pri) rotate(rt,k^1);
            else pushup(rt);
        }
        void remove(int &rt,int x){
            if(!rt) return;
            if(x<t[rt].val) remove(t[rt].s[0],x);
            if(x>t[rt].val) remove(t[rt].s[1],x);
            if(x==t[rt].val){
                if(!t[rt].s[0] && t[rt].s[1]) rotate(rt,0),remove(t[rt].s[0],x);
                else if(t[rt].s[0] && !t[rt].s[1]) rotate(rt,1),remove(t[rt].s[1],x);
                else if(t[rt].s[0] && t[rt].s[1]){
                    int k=t[t[rt].s[0]].pri>t[t[rt].s[1]].pri;
                    rotate(rt,k),remove(t[rt].s[k],x);
                }else{
                    t[rt].cnt--,t[rt].size--;
                    if(!t[rt].cnt) rt=0;
                }
            }
            pushup(rt);
        }
        int rank(int rt,int x){
            if(!rt) return 1;
            if(x<t[rt].val) return rank(t[rt].s[0],x);
            if(x>t[rt].val) return t[t[rt].s[0]].size+t[rt].cnt+rank(t[rt].s[1],x);
            return t[t[rt].s[0]].size+1;
        }
        int kth(int rt,int x){
            if(!rt) return 0;
            if(x<=t[t[rt].s[0]].size) return kth(t[rt].s[0],x);
            if(x>t[t[rt].s[0]].size+t[rt].cnt) return kth(t[rt].s[1],x-t[t[rt].s[0]].size-t[rt].cnt);
            return t[rt].val;
        }
        int pre(int rt,int x){
            if(!rt) return -inf;
            if(x<=t[rt].val) return pre(t[rt].s[0],x);
            return max(t[rt].val,pre(t[rt].s[1],x));
        }
        int suc(int rt,int x){
            if(!rt) return inf;
            if(x>=t[rt].val) return suc(t[rt].s[1],x);
            return min(t[rt].val,suc(t[rt].s[0],x));
        }
    private:
        struct node{
            int s[2];
            int val;
            int size,cnt,pri;
        }t[N];
        int cnt;
        inline void pushup(int rt){t[rt].size=t[t[rt].s[0]].size+t[t[rt].s[1]].size+t[rt].cnt;}
        inline void rotate(int &rt,int x){ // x=0 左   x=1 右
            int k=t[rt].s[x^1];
            t[rt].s[x^1]=t[k].s[x];
            t[k].s[x]=rt;
            pushup(rt),pushup(k);
            rt=k;
        }
};

\(\large\texttt{FHQ Treap}\)

P4036 [JSOI2008] 火星人

需要插入,那么平衡树维护 hash,二分答案。

P3215 [HNOI2011] 括号修复 / [JSOI2011] 括号序列

给定括号序列,需要支持区间修改、翻转、反转,询问至少修改多少位才能使括号序列合法。

设左右括号分别为 \(-1\)\(1\),对于询问可以维护前缀最大值与后缀最小值。

对于翻转操作,我们同时需要维护翻转前后的信息。注意打标记的细节。

对于反转操作,同样可以维护两份信息。每个结点一共要维护四个信息。

FHQ Treap 即可。

启发:先看询问,设计节点信息,再考虑修改标记。

CF702F T-Shirts

首先对人和物品都排序,对人建立平衡树,维护钱数和买的衣服数。对于每个物品,将钱数够的人的钱数减去,买的衣服数 \(+1\)。但是修改后可能会乱掉(值域相交)。

那么就将 \([c,2c]\) 间的数暴力修改,插入左儿子。

posted @ 2025-01-05 22:56  仙山有茗  阅读(13)  评论(0)    收藏  举报