[模板] 平衡树: Splay, 非旋Treap, 替罪羊树

简介

二叉搜索树, 可以维护一个集合/序列, 同时维护节点的 \(size\), 因此可以支持 insert(v), delete(v), kth(p,k), rank(v)等操作.

另外, prev(v) == kth(rt,rank(v)-1);

next(v) == kth(rt,rank(v)+1).

平衡树通过各种方法保证二叉搜索树的平衡, 从而达到 \(O(\log n)\) 的均摊复杂度.

Splay

Splay 不仅可以实现一般平衡树的操作, 还可以实现序列的翻转/旋转等操作.

Splay 被用于LCT的操作, 保证了LCT的各种操作的复杂度也为 \(O(\log n)\).

有关 rotate


Before

After

代码

以前的代码, 似乎不少地方都写麻烦了...

struct tnode{
    int val,cnt,sz,fa,son[2];
    tnode():val(0),cnt(0),sz(0),fa(0){son[0]=son[1]=0;}
    tnode(int f,int v):val(v),cnt(1),sz(1),fa(f){son[0]=son[1]=0;}
};
const int sz=100010;
struct splay{
    int rt,end;
    tnode n[sz];
    
    splay():rt(0),end(0) {}
    
    int addnode(int f,int v){
        n[++end]=tnode(f,v);
        return end;
    }
    
    void update(int p){
        n[p].sz=n[n[p].son[0]].sz+n[n[p].son[1]].sz+n[p].cnt;
    }
    
    void sf(int f,int s,int dir){
        n[f].son[dir]=s;
        n[s].fa=f;
    }
    void rotate(int p){
        int x=n[p].fa,y=n[x].fa;
        int dir=n[x].son[1]==p,dir2=n[y].son[1]==x;
        int z=n[p].son[!dir];
        
        sf(y,p,dir2);
        sf(x,z,dir);
        sf(p,x,!dir);
        
        update(x);
        update(p);
    }
    void sp(int p,int dist=0){
        if(dist==0)rt=p;
        for(int x=n[p].fa;x!=dist;x=n[p].fa){
            if(n[x].fa!=dist)
                rotate(((n[x].son[1]==p)^(n[n[x].fa].son[1]==x))?p:x);
            rotate(p);
        }
    }
    
    void insert(int val){
        if(rt==0){
            rt=addnode(0,val);
            return;
        }
        int now=rt;
        while(now){
            if(n[now].val==val){
                ++n[now].cnt;
                update(now);
                break;
            }
            int dir=n[now].val<val;
            if(n[now].son[dir])
                now=n[now].son[dir];
            else{
                n[now].son[dir]=addnode(now,val);
                now=n[now].son[dir];
                break;
            }
        }
        sp(now);
    }
    
    int min(int p){
        int fa=n[p].fa;
        while(n[p].son[0])
            p=n[p].son[0];
        sp(p,fa);
        return p;
    }
    
    int max(int p){
        int fa=n[p].fa;
        while(n[p].son[1])
            p=n[p].son[1];
        sp(p,fa);
        return p;
    }
    
    int find(int val){
        int now=rt,pr=0;
        while(now){
            pr=now;
            if(n[now].val==val)
                break;
            now=n[now].son[n[now].val<val];
        }
        sp(pr);
        return now;
    }
    
    int findkth(int k){
        int now=rt,pr=0;
        while(now){
            pr=now;
            int lsz=n[n[now].son[0]].sz;
            if(k>lsz&&k<=lsz+n[now].cnt)
                break;
            if(k<=lsz)
                now=n[now].son[0];
            else{
                k-=lsz+n[now].cnt;
                now=n[now].son[1];
            }
        }
        sp(pr);
        return now;
    }
    
    int rank(int val){
        int k=find(val);
        return k?n[n[k].son[0]].sz+1:-1;
    }
    
    void remove(int val){
        if(find(val)==0)return;
        if(n[rt].cnt>1){
            --n[rt].cnt;
            update(rt);
            return;
        }
        int ls=n[rt].son[0],rs=n[rt].son[1];
        if(ls==0&&rs==0){
            rt=0;
            return;
        }
        if((ls==0)^(rs==0)){
            rt=(ls!=0?ls:rs);
            n[rt].fa=0;
            return;
        }
        int newrt=min(rs);
        n[newrt].fa=0;
        n[newrt].son[0]=n[rt].son[0];
        n[n[rt].son[0]].fa=newrt;
        rt=newrt;
        update(rt);
    }
    
    int prev(int val){
        find(val);
        if(n[rt].val>=val)
            return max(n[rt].son[0]);
        return rt;
    }
    
    int next(int val){
        find(val);
        if(n[rt].val<=val)
            return min(n[rt].son[1]);
        return rt;
    }
}s;

非旋Treap

Treap 通过随机权值的堆保证树高度为 \(O(\log n)\).

Treap 可以持久化. (并不会写)

代码

struct tn{int v,p,sz,ch[2];}fhq[200060];
int rt=0,pf=0;
int newnd(int v){fhq[++pf]=(tn){v,rand(),1,{0,0}};return pf;}
void update(int p){fhq[p].sz=fhq[fhq[p].ch[0]].sz+fhq[fhq[p].ch[1]].sz+1;}

void split(int rt,int v,int &tl,int &tr){
    if(rt==0){tl=tr=0;return;}
    if(fhq[rt].v<=v)
        tl=rt,split(fhq[rt].ch[1],v,fhq[rt].ch[1],tr);
    else
        tr=rt,split(fhq[rt].ch[0],v,tl,fhq[rt].ch[0]);
    update(rt);
}
int merge(int tl,int tr){
    if(tl==0||tr==0)return tl+tr;
    if(fhq[tl].p<=fhq[tr].p){
        fhq[tl].ch[1]=merge(fhq[tl].ch[1],tr);
        update(tl);
        return tl;
    }
    else{
        fhq[tr].ch[0]=merge(tl,fhq[tr].ch[0]);
        update(tr);
        return tr;
    }
}
int kth(int &rt,int k){
    int now=rt;
    while(1){
        int tmp=fhq[fhq[now].ch[0]].sz;
        if(k<=tmp)now=fhq[now].ch[0];
        else if(k==tmp+1)return now;
        else now=fhq[now].ch[1],k-=tmp+1;
    }
}
void insert(int &rt,int v){
    int x,y;
    split(rt,v,x,y);
    rt=merge(merge(x,newnd(v)),y);
}
void remove(int &rt,int v){
    int x,y,z;
    split(rt,v,x,z);
    split(x,v-1,x,y);
    y=merge(fhq[y].ch[0],fhq[y].ch[1]);
    rt=merge(merge(x,y),z);
}
int rank(int &rt,int v){
    int x,y;
    split(rt,v-1,x,y);
    int tmp=fhq[x].sz+1;
    rt=merge(x,y);
    return tmp;
}
int prev(int &rt,int v){
    return kth(rt,rank(rt,v)-1);
}
int next(int &rt,int v){
    return kth(rt,rank(rt,v+1));
}
void print(){
    printf("dbg rt=%d\n",rt);
    printf("i  v p sz ch[0] ch[1]\n");
    rep(i,0,pf)printf("%d  %d %d %d %d %d\n",i,fhq[i].v,fhq[i].p,fhq[i].sz,fhq[i].ch[0],fhq[i].ch[1]);
    printf("dbgend\n");
}

替罪羊树

替罪羊树定义一个值 \(\alpha\), 如果左子树点数/右子树点数 > 整个子树点数\(\cdot \alpha\), 将这个子树重构.

根据势能分析, 均摊复杂度为单次操作 \(O(\log n)\) . 不会证

显然\(0.5 < \alpha < 1\), 但 \(\alpha\) 取值过大或过小都会影响代码运行效率. \(\alpha\) 可以取 \(0.7\), \(0.75\), \(0.8\) 等值, 效率没有明显差距. 这里取 \(\alpha = 0.75\).

由于替罪羊树就是有重构的二叉搜索树, 它较为容易实现, 并且常数较小.

由于替罪羊树仅仅依赖重构操作, 它还可以实现一些奇怪的操作, 比如 K-D Tree, 动态区间第k大(替罪羊树套权值线段树)等.

代码

一遍过真开心

const db alp=0.75;
//szp: number of points(includes deleted ones);
//szr: number of values(not include deleted ones; point*cnt)
struct tnd{int v,szr,szp,cnt,ch[2];}tree[nsz];
#define ls(p) tree[p].ch[0]
#define rs(p) tree[p].ch[1]
il bool isbad(int p){return tree[ls(p)].szp>tree[p].szp*alp||tree[rs(p)].szp>tree[p].szp*alp;}
il void pu(int p){
	tree[p].szp=tree[ls(p)].szp+tree[rs(p)].szp+1;
	tree[p].szr=tree[ls(p)].szr+tree[rs(p)].szr+tree[p].cnt;
}

int rt=0,pt=0,deled[nsz],pd=0;
il void init(int p,int v){tree[p]=(tnd){v,1,1,1,{0,0}};}
il int newnd(int v){
	int p=(pd?deled[pd--]:++pt);
	init(p,v);
	return p;
}

int li[nsz],pl=0;
void pia(int p){
	if(p==0)return;
	pia(ls(p));
	if(tree[p].cnt)li[++pl]=p;
	else deled[++pd]=p;
	pia(rs(p));
}
void build(int &rt,int rl,int rr){
	if(rl>rr){rt=0;return;}//important
	int mid=(rl+rr)>>1;
	rt=li[mid];
	build(ls(rt),rl,mid-1);
	build(rs(rt),mid+1,rr);
	pu(rt);
}
void rebuild(int &rt){
//	printf("RB %d\n",rt);
	pl=0;
	pia(rt);
	build(rt,1,pl);
}

void insert(int v,int &rt){
	if(rt==0){rt=newnd(v);return;}
	if(v==tree[rt].v)++tree[rt].cnt;
	else if(v<tree[rt].v)insert(v,ls(rt));
	else insert(v,rs(rt));
	pu(rt);
	if(isbad(rt))rebuild(rt);
}
void remove(int v,int rt){
	if(rt==0)return;
	if(v==tree[rt].v){if(tree[rt].cnt)--tree[rt].cnt;}
	else if(v<tree[rt].v)remove(v,ls(rt));
	else remove(v,rs(rt));
	pu(rt);
}

int rk(int v,int rt){
	int ans=1;
	while(rt){
		if(v==tree[rt].v){ans+=tree[ls(rt)].szr;break;}
		else if(v<tree[rt].v)rt=ls(rt);
		else ans+=tree[ls(rt)].szr+tree[rt].cnt,rt=rs(rt);
	}
	return ans;
}
int kth(int k,int rt){
	int fl=0;
	while(rt){
		if(k<=tree[ls(rt)].szr)rt=ls(rt),fl=0;
		else{
			k-=tree[ls(rt)].szr;
			if(k<=tree[rt].cnt)return tree[rt].v;
			else k-=tree[rt].cnt,rt=rs(rt),fl=1;
		}
	}
	return fl?1e8:-1e8;
}

int prev(int v){return kth(rk(v,rt)-1,rt);}
int next(int v){return kth(rk(v+1,rt),rt);}

用于测试的输入/输出

由于某谷样例较弱, 窝就造了一个...

// it also tests rebuild() func in Scapegoat Tree
// and invalid input (output -inf/inf in my code)
// sample input
21
1 5
1 4
1 5
2 4
1 3
1 2
1 1
1 0
4 3
3 4
3 6
1 15
1 14
1 13
1 12
1 11
5 5
6 5
5 0
6 15
4 20

// sample output
2
5
7
3
11
-100000000
100000000
100000000

// output with "RB" (aka rebuild())
i=1
i=2
i=3
i=4
i=5
i=6
i=7
RB 1
i=8
i=9
2
i=10
5
i=11
7
i=12
i=13
i=14
RB 3
i=15
i=16
i=17
3
i=18
11
i=19
-100000000
i=20
100000000
i=21
100000000
posted @ 2019-02-18 16:12  Ubospica  阅读(296)  评论(0编辑  收藏  举报