平衡树学习小记

总起

修炼了2天,终于差不多完成基础了,数据结构都是很灵活的,不仅是应用,而且写代码也是有很多值得思考的地方。而在平衡树中,旋转是核心的核心。

先总结一下吧。

先说明一些概念

键值,所谓的key,我一般用val表示,就是当前点存的值。

ind(ex),引索,就是用平衡树要维护的东西,可能还用wei(ght)来表示。相当于普通序列中的下标。

虚拟节点:第n+1个点,放在所有点之前,让平衡树有头;第n+2个点,放在最后,让平衡树有尾。这两个点没有实际意义,用作连接。

siz(e),以当前点为根的子树的点的个数,不包括虚拟点。

cnt,有时候不离散化要来统计相同key的点个数。

root或top,平衡树的根。

Treap

之前感觉splay有点难搞,所以先打一发treap。

treap的核心,就是每一个点的weight。它在保持搜索树同时,要维护weight,保持一个weight的小根堆。其中weight是随机生成的。(随机这种东西要记住啊!!!)

几个操作简介

1,旋转。只有单旋,很朴素,没什么好讲的;

2,插入一个点X。先像普通BST一样插入,然后当X成为了叶节点的时候,一步步把它像栈up上去,保持父亲的weight比儿子的大。

3,删除一个点X。其实有两种。1)把 X down到底下做成叶节点,直接删除,很简洁;2)把X up到顶分裂合并,这个比较麻烦。

4,分裂。即把一棵treap分成左右两边,其中满足SBT的性质。这个后面splay再说。

5,合并。其实就是分裂反过来。

6,查找。可以利用size来查。也可以直接用SBT的方式查。查找第K大,找元素都很方便。

优点是很明显的:十分容易调,超级简洁。很多都是递归完成,核心也很少。还能可持久化。

缺点:weight局限了它,它不能用来维护序列,只能处理朴素问题。

排序的应用代码

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
typedef long long ll;
const int N=2000005,mx=100000007;
const ll pri=1000007,mo=6451342;
            //0 left 1 right
int siz[N],tr[N][2],up[N],wei[N],key[N],a[N],n,i,j,cnt[N],top,t1,pos,ans[N];
int pd(int x)
{
    return (tr[up[x]][1]==x);
}
void update(int x)
{
    siz[x]=siz[tr[x][0]]+siz[tr[x][1]]+cnt[x];
    if (siz[39]==30)
    {
        pos=i;
    }
}
void rotate(int x)
{
    int y=up[x],z=pd(x);
    up[x]=up[y];
    if (up[y]) tr[up[y]][pd(y)]=x;
    tr[y][z]=tr[x][1-z];
    if (tr[x][1-z]) up[tr[x][1-z]]=y;
    tr[x][1-z]=y;
    up[y]=x;
    if (top==y) top=x;
    update(y);
    update(x);
}
void insert(int x,int y,int z)
{
    if (z==key[x])
    {
        siz[x]++;
        cnt[x]++;
        return;
    }
    if (x!=0) siz[x]++;
    if (x==0)
    {
        t1++;
        key[t1]=z;
        wei[t1]=(ll)(z+100)*pri%mo;
        siz[t1]=1;
        up[t1]=y;
        cnt[t1]++;
        int kan=(z<key[y])?0:1;
        tr[y][(z<key[y])?0:1]=t1;
        return;
    }
    if (z<key[x])
    {
        insert(tr[x][0],x,z);
        if (wei[tr[x][0]]<wei[x]) rotate(tr[x][0]);
    }
    else
    {
        insert(tr[x][1],x,z);
        if (wei[tr[x][1]]<wei[x]) rotate(tr[x][1]);
    }
}
void dele(int y)
{
    int x=top;
    while (key[x]!=y)
    {
        if (y<key[x])
            x=tr[x][0];
        else
            x=tr[x][1];
    }
    if (cnt[x]==1)
    while (tr[x][0]!=0||tr[x][1]!=0)
    {
        if (wei[tr[x][0]]<wei[tr[x][1]]) rotate(tr[x][0]);
        else
        rotate(tr[x][1]);
    }
    y=x;
    cnt[x]--;
    while (x)
    {
        update(x);
        x=up[x];
    }
    if (!cnt[y])
    {
        tr[up[y]][pd(y)]=0;
        key[y]=0;
        wei[y]=0;
        up[y]=0;
    }
}
int find(int x)
{
    int ret=0,y=top;
    while (key[y]!=x)
    {
        if (x<key[y]) y=tr[y][0];else
        {
            ret=ret+siz[y]-siz[tr[y][1]];
            int kan=siz[tr[y][0]]+cnt[y];
            y=tr[y][1];
        }
    }
    ret+=siz[tr[y][0]]+cnt[y];
    cnt[y]--;
    return ret;
}
int main()
{
    freopen("sort.in","r",stdin);
    freopen("treap.out","w",stdout);
    scanf("%d",&n);
    wei[0]=mx+1;
    fo(i,1,n) scanf("%d",a+i);
    fo(i,1,n)
    {
        if (top==0)
        {
            top=1;
            t1=1;
            key[t1]=a[i];
            wei[t1]=(ll)(a[i]+100)*pri%mo;
            siz[t1]=1;
            cnt[t1]++;
            continue;
        }
        insert(top,0,a[i]);
        dele(a[i]);
        insert(top,0,a[i]);
    }
    fo(i,1,n)
    {
        pos=find(a[i]);
        ans[pos]=a[i];
    }
    fo(i,1,n) printf("%d\n",ans[i]);
}

应用在排序容易检验各种操作的正确性,容易拍。在最后的求答案中,怎么样求答案都是行的。比如分离后又合并等。

treap让我逐步建立了对于平衡树的思维方式。

Splay

数据结构优美的特点在于函数化,只要函数正确,维护的代码是很可打的。而Splay很灵活,操作很多,需要用脑想,去简洁化,然后就可以写得又爽又快。

splay没有什么拘束,ind或者说weight的值不一定要刻意维护,自己想怎么玩弄它就怎么玩,想想就很爽!

Splay的核心:单旋转与双旋转

单旋转

不管是左儿子还是右儿子旋转上去,我们都可以直接用一段代码完成,很明了,不记得了就画个图,找找套路。

void rotate(int x)
{
    int y=up[x],z=pd(x);//pd函数为判断节点X是它父亲的左儿子或者右儿子
    down(y);//下传懒标记
    down(x);//下传懒标记
    up[x]=up[y];
    if (up[y]) tr[up[y]][pd(y)]=x;
    tr[y][z]=tr[x][1-z];
    if (tr[y][z]) up[tr[y][z]]=y;
    up[y]=x;
    tr[x][1-z]=y;
    update(y);//更新节点信息
    update(x);//注意两个update的顺序
}

双旋转

这是精华吧,网上的叙述很完备了,值得注意的是Zig-Zig时要先旋转父亲,再旋转儿子。

下面的splay函数,包括了双旋与单旋,而实质上都是单旋构成的。

void splay(int x,int y)
{
    while (up[x]!=y)
    {
        if (up[up[x]]!=y)
            if (pd(x)==pd(up[x])) rotate(up[x]);else rotate(x);
        rotate(x);
    }
    if (!y) root=x;
}

插话:维护序列

splay维护序列,实际上就是把序列下标作为BST的ind,然后就可以乱搞了。想一想:如果要让一颗子树只包含下标为l~r要怎么搞。

其他重要操作

这一部分要学好,我觉得要操作和题目配合着看,效果更好,但限于时间只能打点操作了。

1,合并x,y。

两颗splay合起来,保证x的所有元素的ind比y的大(注:有时候,ind的值不用记的,也不用刻意维护,看情况用),只需要让x进行“光头处理”,即让x的根节点(谁做根没关系)只有左儿子。

这之后x直接把y的根作为右儿子就行了,同时注意总根为x的根。

就算对y进行光头处理再类似地做也是一点问题都没有的。

2,分离

随便搞搞,看题目来。

3,找最值

我们类似于线段树一样,给每个点x都定义一个minium,或者maxinum,表示以x为根的子树,key值的最值。

4,玩弄序列时,在不维护index的情况下找原序列下标为K的元素

我们不是有siz吗?对于根的左边,ind即使没有记录,我们也知道左子树的所有点ind比根小,所以实际上已经是有序的了。细节比较难以表述,其实挺容易想懂,不写了~~~

5,不维护index,翻转序列

我们要把原序列中下标为l~r的元素下标全部翻转,即r变成l,r-1变为l+1····很简单,只需要把l~r搞在一棵子树里,然后对于每个点直接交换左右子树位置即可。当然了,为了保证时间复杂度,打懒标记,到时候碰到才下传。

6,区间修改和单点修改

7,····

感受

学了一会,就喜欢上了splay、treap,他们变化多端,splay可以搞LCT,treap可以可持久化,他们还都可以树套树···平时没事干,多多在脑海里splay一下,treap更新一下,会有收获。每次打的平衡树,都会根据实际情况变化,实在是有趣。

切题

JZOJ1149:又是排序,这次放splay

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
typedef long long ll;
const int N=500005;
int tr[N][2],siz[N],up[N],key[N],index[N],i,j,n,root,t1,a[N],ans,kan;
int pd(int x)
{
    return (tr[up[x]][1]==x);
}
void rotate(int x)
{
    int y=up[x],z=pd(x);
    up[x]=up[y];
    if (up[x]) tr[up[x]][pd(y)]=x;
    tr[y][z]=tr[x][1-z];
    if (tr[x][1-z]) up[tr[x][1-z]]=y;
    up[y]=x;
    tr[x][1-z]=y;
}
void insert(int x,int y,int z,int bef)
{
    if (!x)
    {
        t1++;
        key[t1]=y;
        index[t1]=z;
        up[t1]=bef;
        if (!bef) return;
        if (t1>=34858)
        {
            t1=t1+1-1;
        }
        if (y<key[bef]) tr[bef][0]=t1;else tr[bef][1]=t1;
        return;
    }
    kan=x;
    if (y<key[x])
        insert(tr[x][0],y,z,x);
    else
        insert(tr[x][1],y,z,x);
}
void splay(int x,int y)
{
    while (up[x]!=y)
    {
        if (up[up[x]]!=y)
            if (pd(x)==pd(up[x])) rotate(up[x]);
                else
            rotate(x);
        rotate(x);
    }
    if (!y) root=x;
}
int getmin(int x)
{
    if (!tr[x][0]) return x;
    else return getmin(tr[x][0]);
}
int getmax(int x)
{
    if (!tr[x][1]) return x;
    else return getmax(tr[x][1]);
}
int merge(int x1,int x2)
{
    up[x1]=0;
    up[x2]=0;
    if (!x1) return x2;
    else if (!x2) return x1;
    int l=getmax(x1);
    splay(l,0);
    tr[l][1]=x2;
    up[x2]=l;
    return l;
}
int main()
{
    freopen("sort.in","r",stdin);
    //freopen("splay.out","w",stdout);
    scanf("%d",&n);
    fo(i,1,n) scanf("%d",a+i);
    fo(i,1,n)
    {
        if (!t1)
        {
            t1++;
            key[t1]=a[i];
            index[t1]=i;
            root=t1;
            continue;
        }
        insert(root,a[i],i,0);
        splay(t1,0);
    }
    fo(i,1,n)
    {
        ans=getmin(root);
        printf("%d\n",key[ans]);
        splay(ans,0);
        root=merge(tr[root][0],tr[root][1]);
    }
}

JZOJ1960 最大值2,区间修改,区间询问。

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
typedef long long ll;
const int N=200005;
const int inf=2147483646;
int tr[N][2],val[N],ind[N],siz[N],mx[N],up[N],n,i,j,k,root,t1,last,a[N],x,y,kase,tmp,tp,pos,m,des[N],z;
void down(int x)
{
    if (!x||!des[x]) return;
    if (tr[x][0]) des[tr[x][0]]+=des[x];
    if (tr[x][1]) des[tr[x][1]]+=des[x];
    mx[x]+=des[x];
    val[x]+=des[x];
    des[x]=0;
}
void update(int x)
{
    down(x);
    down(tr[x][0]);
    down(tr[x][1]);
    mx[x]=max(val[x],max(mx[tr[x][0]],mx[tr[x][1]]));
}
int pd(int x)
{
    return tr[up[x]][1]==x;
}
void rotate(int x)
{
    int y=up[x],z=pd(x);
    update(y);
    update(x);
    up[x]=up[y];
    if (up[y]) tr[up[x]][pd(y)]=x;
    tr[y][z]=tr[x][1-z];
    if (tr[y][z]) up[tr[x][1-z]]=y;
    up[y]=x;
    tr[x][1-z]=y;
    update(y);
    update(x);
}
void splay(int x,int y)
{
    while (up[x]!=y)
    {
        if (up[up[x]]!=y)
            if (pd(x)==pd(up[x])) rotate(up[x]);
                else
            rotate(x);
        rotate(x);
    }
    if (!y) root=x;
}
int find(int x,int y)
{
    while (ind[x]!=y)
    {
        down(x);
        if (y<ind[x]) x=tr[x][0];
        else x=tr[x][1];
    }
    return x;
}
int main()
{
    freopen("max2.in","r",stdin);
    freopen("max2.out","w",stdout);
    scanf("%d",&n);
    last=n+1;
    root=n+1;
    fo(i,1,n)
    {
        scanf("%d",a+i);
        up[i]=last;
        tr[last][1]=i;
        val[i]=a[i];
        ind[i]=i;
        last=i;
    }
    t1=n+2;
    ind[n+1]=0;
    ind[n+2]=n+1;
    tr[n][1]=n+2;
    mx[0]=val[0]=val[n+2]=val[n+1]=-inf;
    up[n+2]=n;
    x=n+2;
    while (x)
    {
        mx[x]=max(mx[tr[x][1]],val[x]);
        x=up[x];
    }
    splay(n+2,0);
    scanf("%d",&m);
    fo(i,1,m)
    {
        scanf("%d%d%d",&kase,&x,&y);
        tmp=find(root,y+1);//pos==x?         find ind which ==y+1
        splay(tmp,0);
        tmp=tr[tmp][0];
        tp=find(tmp,x-1);
        splay(tp,root);
        if (kase==1)
        {
            scanf("%d",&z);
            des[tr[tp][1]]=z;
            down(tr[tp][1]);
        }
        else
            printf("%d\n",mx[tr[tp][1]]);
    }
}

JZOJ3599 机械臂排序,涉及翻转

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
typedef long long ll;
const int N=200005,inf=2147483646;
struct re
{
    int a,b;
}b[N];
int mn[N],tr[N][2],siz[N],val[N],up[N],n,i,j,l,r,root,t1,last,a[N],x,tmp,tp,z,des[N],dep[N],dur,base;
void down(int x)
{
    if (!x) return;
    if (des[x])
    {
        int z=tr[x][0];
        tr[x][0]=tr[x][1];
        tr[x][1]=z;
        if (tr[x][0]) des[tr[x][0]]^=1;
        if (tr[x][1]) des[tr[x][1]]^=1;
        des[x]=0;
    }
}
void update(int x)
{
    down(tr[x][0]);
    down(tr[x][1]);
    mn[x]=min(mn[tr[x][0]],min(mn[tr[x][1]],val[x]));
    siz[x]=siz[tr[x][0]]+siz[tr[x][1]]+dep[x];
}
int pd(int x)
{
    return tr[up[x]][1]==x;
}
void rotate(int x)
{
    int y=up[x],z=pd(x);
    update(y);
    update(x);
    up[x]=up[y];
    if (up[y]) tr[up[y]][pd(y)]=x;
    tr[y][z]=tr[x][1-z];
    if (tr[y][z]) up[tr[y][z]]=y;
    up[y]=x;
    tr[x][1-z]=y;
    update(y);
    update(x);
}
void splay(int x,int y)
{
    while (up[x]!=y)
    {
        if (up[up[x]]!=y)
            if (pd(x)==pd(up[x])) rotate(up[x]);else rotate(x);
        rotate(x);
    }
    if (!y) root=x;
}
int find(int x,int y)
{
    int ret=0;
    while (y==0||y!=0)
    {
        down(x);
        if (ret+siz[tr[x][0]]+dep[x]>y)
            x=tr[x][0];
        else
        if (ret+siz[tr[x][0]]+dep[x]==y)
            break;
        else
        {
            ret+=siz[tr[x][0]]+dep[x];
            x=tr[x][1];
        }
    }
    return x;
}
int getmin(int x)
{
    down(x);
    int ret=0;
    while (val[x]!=mn[x]||(val[x]==mn[x]&&mn[tr[x][0]]==mn[x]))
    {
        if (!x) break;
        if (mn[tr[x][0]]==mn[x]) x=tr[x][0];else
        {
            ret+=siz[tr[x][0]]+dep[x];
            x=tr[x][1];
        }
        down(x);
    }
    dur=val[x];
    return ret+siz[tr[x][0]]+dep[x];
}
bool cmp(re a,re b)
{
    return (a.a<b.a||(a.a==b.a&&a.b<b.b));
}
int main()
{
    freopen("mach.in","r",stdin);
    freopen("sb1.out","w",stdout);
    scanf("%d",&n);
    last=n+1;
    root=n+1;
    fo(i,1,n) scanf("%d",a+i);
    //离散化
    fo(i,1,n)
    {
        b[i].a=a[i];
        b[i].b=i;
    }
    sort(b+1,b+1+n,cmp);
    fo(i,1,n)
        a[b[i].b]=i;
    fo(i,1,n)
    {
        up[i]=last;
        val[i]=a[i];
        tr[last][1]=i;
        dep[i]=1;
        last=i;
    }
    up[n+2]=n;
    tr[n][1]=n+2;
    mn[0]=val[0]=val[n+2]=val[n+1]=inf;
    dep[n+2]=1;
    x=n+2;
    while (x)
    {
        update(x);
        x=up[x];
    }
    splay(n+2,0);
    fo(i,1,n)
    {
        splay(l=find(root,i-1),0);
        tmp=getmin(tr[l][1]);//return的东西不对
        if (i<n) printf("%d ",tmp+i-1);
        splay(r=find(tr[l][1],tmp+1),root);
        des[tr[r][0]]^=1;
        down(tr[r][0]);
    }
    printf("%d",tmp+n-1);
}

题目不断更新···

时间: 2024-09-29 02:45:36

平衡树学习小记的相关文章

git 学习小记之记住https方式推送密码

昨天刚刚学了点git基础操作,但是不幸的是[email protected]给出公告说尽量使用 https 进行操作.可是在用 https 进行 push 时,都需要输入帐号和密码. 各种百度谷歌之后在[email protected]官网找到了解决方法<https方式使用[email protected]设置密码的方式>文中给出了几个方法,并且都非常简单. 关于 cache 缓存方式,我不太喜欢,因为要设置时间,而且会过期.而 store 相应的非常方便,设置全局后,方便多个库使用.当然如果

linux学习小记 (一 )

shell 学习小记: 注意:多看系统脚本  多模仿    su切换用户时需要输入目标用户密码,root(superuser)切换到任何用户都不需要输入密码,- 参数必须要是最后一个(su huhu -) sudo需要输入当前用户密码,拥有sudo特权的用户可以执行 "sudo su -"命令,使用自己的密码切换到root用户 , 所以应该在/etc/sudoers 文件中禁止 sudo 执行su命令 linux文件与颜色: /etc/DIR_COLORS   (命令dircolors

[平衡树学习笔记]那些你所不知道的鬼畜写法

treap:  (Orz fhq 大神,我曾经以为我会了 treap ,就再也不会写 splay 了,然后我遇上了 lct ) 1 #include <cstdlib> 2 const int sizeOfMemory=10000; 3 template <class type> inline void swap(type & x, type & y) {type t=x; x=y; y=t;} 4 5 namespace treap 6 { 7 struct no

logstash 学习小记

logstash 学习小记 标签(空格分隔): 日志收集 Introduce Logstash is a tool for managing events and logs. You can use it to collect logs, parse them, and store them for later use (like, for searching). – http://logstash.net 自从2013年logstash被ES公司收购之后,ELK stask正式称为官方用语.非

js 正则学习小记之匹配字符串优化篇

昨天在<js 正则学习小记之匹配字符串>谈到 /"(?:\\.|[^"])*"/ 是个不错的表达式,因为可以满足我们的要求,所以这个表达式可用,但不一定是最好的.从性能上来说,他非常糟糕,为什么这么说呢,因为 传统型NFA引擎 遇到分支是从左往右匹配的,所以它会用 \\. 去匹配每一个字符,发现不对后才用 [^"] 去匹配.比如这样一个字符串: "123456\'78\"90"共 16 个字符,除了第一个 " 直接

js 正则学习小记之匹配字符串

原文:js 正则学习小记之匹配字符串 今天看了第5章几个例子,有点收获,记录下来当作回顾也当作分享. 关于匹配字符串问题,有很多种类型,今天讨论 js 代码里的字符串匹配.(因为我想学完之后写个语法高亮练手,所以用js代码当作例子) var str1 = "我是字符串1哦,快把我取走", str2 = "我是字符串2哦,快把我取走"; 比如这样一个字符串,匹配起来很简单 /"[^"]*"/g 即可. PS: 白色截图是 chrome 3

git 学习小记之图形化界面客户端

习惯了 Windows 的用户,一直不喜欢用类似命令行的东西来操作,当然我也不是不喜欢,只是操作太慢了.也许 Linux 大神在命令行的帮助下,办事效率翻倍,那也是非常常见的事情..当然我不是大神,所以还是得选择一个合适的工具才行. 其实相信大家也是一样,简单学习之后,直接就上工具了..我找到一篇不错的文章<Git图形化界面客户端大汇总>里面介绍了 11 款 git 工具,而且有截图和简单描述,貌似作者按喜好排序的. 我下载了前三个测试了下.TortoiseGit 差不多直接上手,因为我用的

数据库规范——学习小记

数据库规范--学习小记 之前在学习后端开发的时候,在数据库这一块,有关建表与查询效率等方面一直存有疑问,但因为做的项目都很小,不太需要考虑效率与规范,所以想着能实现功能就行.因此最近打算深入了解一些建表的规范以及如何提高查询效率. 表设计规范 命名规范的话,之前倒是都有注意,大致总结为以下几点: 表命名 表名前应该加上前缀,表的前缀一个用系统或模块的英文名称缩写,然后驼峰式命名 表名应该有意义,易于理解,最好使用可以表达功能的英文单词或缩写,如果用英文单词表示,建议使用完整的英文单词 表名最好不

Java基础学习小记--多态

题外话:总结了多年的学习心得,不得不说,睡眠是一个学习者的必需品!所谓“早起毁一天”不是没有道理哪,特别对Coders来说,有几天不是加班到夜里.好吧,我承认对于初学Java的我,昨日头脑顿路,Java的继承与多态,看得我是有点小懵逼.其实大致是懂了,不过到了具体应用中,特别是比较复杂的继承和调用关系的时候些许迷路.好了,话不多说,明日再把继承和多态来个总结,今日先对多态来个小记. ---------------------------------------------------------