首页 > 其他分享 >平衡树 学习笔记

平衡树 学习笔记

时间:2024-05-27 21:13:46浏览次数:16  
标签:ch return val int 笔记 学习 平衡 size define

BST

又称二分查找树,\(BST\) 性质指其左子树所有节点全职均小于该点,其右子树所有节点全职均大于该点;同时若对该棵树进行中序遍历,所产生的序列为从小到大排序的序列。

利用该性质,从而在 \(O(\log(n))\) 的复杂度内实现查询排名、第 \(k\) 小(大)值、前驱、后继等。

当每次插入的数据呈单调性时,其内部将形成一条链,从而使复杂度退化为 \(O(n)\) 。

Treap

当数据随机时,其内部时趋于平衡的,\(Treap\) 就是基于随机化与二叉堆使其实现平衡。

对于如下的几种基本操作单次复杂度均为 \(O(\log(n))\) 。

旋转

对于内一个点赋予其一个随机值,使这棵树是该随机值的二叉堆。

具体如何旋转操作在 \(splay\) 那里会有更详细的解释。

旋转操作是使 \(Treap\) 达到平衡的核心。

旋转操作
void rotate(int &p,bool d)
{
    int son=f.ch[d];
    f.ch[d]=t[son].ch[d^1];
    t[son].ch[d^1]=p;
    pushup(p);
    pushup(p=son);
}

插入

找到其应插入的位置,若书中已存在该值则 \(cnt++\) ,否则创建新点,同时根据其随机值旋转,使达到平衡。

插入操作
void insert(int &p,int x)
{
    if(p==0)
    {
        p=++tot;
        f.cnt=f.size=1;
        f.val=x;
        f.dat=rand();
        return ;
    }
    f.size++;
    if(f.val==x) 
    {
        f.cnt++;
        return ;
    }
    bool d=f.val<x;
    insert(f.ch[d],x);
    if(f.dat>t[f.ch[d]].dat) rotate(p,d);
}

删除

找到所要删除点所在位置,如果其 \(cnt>1\) ,直接 \(cnt--\) 即可。

否则,如果该节点只有一个子节点,那么直接让子节点替换其位置即可;若该点为叶子节点,那么直接删掉它不会产生任何其他的影响。

因为 \(Treap\) 支持旋转操作,将其直接旋转到满足上面两种情况即可。

删除操作
void remove(int &p,int x)
{
    if(p==0) return ;
    if(f.val==x)
    {
        if(f.cnt>1)
        {
            f.cnt--;
            f.size--;
            return ;
        }
        bool d=ls.dat>rs.dat;
        if(f.l==0||f.r==0) p=f.l+f.r;
        else rotate(p,d),remove(p,x);
    }
    else f.size--,remove(f.ch[f.val<x],x);
}

查询排名

\(x\) 的排名定义为比 \(x\) 小的个数 \(+1\) 。

因为其满足 \(BST\) 性质,从根节点开始向下寻找,设当前点权值为 \(val\) ,\(ls、rs\) 分别指左右儿子,\(cnt\) 指该节点重复插入次数,\(size\) 指对应节点子树的大小。

  • \(val==x\) ,即已经找到答案,返回 \(ls.size+1\) 。

  • \(val>x\) ,继续向其左儿子方向寻找。

  • \(val<x\) ,继续向右儿子方向寻找,同时答案加上 \(ls.size+cnt\) 。

在很多时候要查询的数并不在树里,但同时根据比他小的个数加一仍可以求出其排名,常见解决办法有两种,不会对复杂度产生较大影响。

  • 在查询前先插入该点,查询后再将该点删除。

  • 当寻找到一个不存在的点时,显然只有所查询数不在树中时才可能出现这种情况,此时令他返回 \(1\) 而不是 \(0\) ,即比他小的个数 \(+1\) 。

查询排名
int ask_rk(int p,int x)
{
    if(p==0) return 1;
    if(f.val==x) return ls.size+1;
    if(f.val>x) return ask_rk(f.l,x);
    return ask_rk(f.r,x)+ls.size+f.cnt;
}

查询第 \(k\) 小(大)值

以查询第 \(k\) 小值为例,因为其满足 \(BST\) 性质,设 \(ls、rs\) 分别指左右儿子,\(size\) 指对应节点子树的大小,\(cnt\) 指该节点重复插入次数。

  • \(ls.size>=k\) ,继续向其左儿子方向寻找。

  • \(ls.size<k\&\&ls.size+cnt>=k\) ,该节点即为答案。

  • \(ls.size+cnt<k\) ,另 \(k\) 减去 \(ls.size+cnt\) ,继续向其右儿子方向寻找。

查询第 k 小值
int ask_val(int p,int x)
{
    if(p==0) return inf;
    if(ls.size>=x) return ask_val(f.l,x);
    if(ls.size+f.cnt>=x) return f.val;
    return ask_val(f.r,x-ls.size-f.cnt);
}

查询前驱、后继

以前驱为例。

根据 \(BST\) 性质,找到第一个小于其的位置,之后不停向右儿子方向寻找。

查询前驱
int pre(int p,int x)
{
    if(p==0) return -inf;
    if(f.val>=x) return pre(f.l,x);
    return max(pre(f.r,x),f.val);
}
查询后继
int nxt(int p,int x)
{
    if(p==0) return inf;
    if(f.val<=x) return nxt(f.r,x);
    return min(nxt(f.l,x),f.val);
}

fhq_Treap

又名无旋 \(Treap\) ,以分裂与合并为核心操作代替旋转使其实现平衡。

其分裂与合并分为按照权值合并与按照树的大小合并两种,当维护序列时按照大小分则更加方便,此处以按照权值分为例,另一种将在文艺平衡树中讲解。

分裂

将 \(p\) 分为 \(x、y\) 两部分,\(x\) 中节点全部 \(\leq k\) ,\(y\) 中节点全部 \(>k\) 。

  • \(val>k\) ,说明其左子树全部在 \(x\) 内,故 \(y=p\) ,向左儿子方向继续分裂。

  • 反之,其右子树全部在 \(y\) 内,则 \(x=p\) ,继续向右儿子方向分裂。

分裂操作
void split(int p,int k,int &x,int &y)
{
    if(!p) {x=y=0; return ;}
    if(f.val>k) y=p,split(f.l,k,x,t[y].l);
    else x=p,split(f.r,k,t[x].r,y);
    pushup(p);
}

合并

通常是将被分开的两部分再合并起来,将 \(x、y\) 合并成 \(p\) ,满足 \(x\) 中节点全部小于 \(y\) 中节点。

此时就可以按照其随机值合并,使其平衡。

合并操作
int merge(int x,int y)
{
    if(!x||!y) return x+y;
    int p;
    if(t[x].dat>t[y].dat)t[x].r=merge(t[x].r,y),pushup(p=x);
    else t[y].l=merge(x,t[y].l),pushup(p=y);
    return p;
}

插入

将 \(k\) 插入。

将 \(root\) 按照 \(k-1\) 分裂为 \(x、y\) ,再按照 \(x、new、y\) 合并起来。

插入操作
void insert(int &p,int k)
{
    t[++tot].val=k,t[tot].size=1,t[tot].dat=rand();
    int x,y;
    split(p,k-1,x,y);
    p=merge(merge(x,tot),y);
}

删除

将 \(k\) 删除。

依旧将 \(root\) 按照 \(k-1\) 分裂成 \(x、y\) ,再将 \(y\) 按照 \(k\) 分裂成 \(is、y\) ,将其按照 \(x、is.ls,is.rs,y\) 合并。

删除操作
void remove(int &p,int k)
{
    int x,y,is;
    split(p,k-1,x,y),split(y,k,is,y);
    p=merge(merge(x,merge(t[is].l,t[is].r)),y);
}

查询排名

查 \(k\) 的排名。

将 \(root\) 按照 \(k-1\) 分裂为 \(x、y\) ,\(x.size+1\) 即为所求。

查询排名
int ask_rk(int p,int k)
{
    int x,y,is;
    split(p,k-1,x,y);
    is=t[x].size+1;
    p=merge(x,y);
    return is;
}

其余操作与 \(Treap\) 基本一致。

优点

  1. 为所有平衡树中最好打,最容易理解的一种。

  2. 因为不需要旋转,所以可以可持久化。

  3. 允许有权值一样的不同节点,因为其是按照权值分裂的。

  4. 因为按照权值分裂,所以不在树中的数也可以直接查。

  5. 当其按照树的大小分裂时,可以实现维护序列,这是 \(Treap\) 做不到的,大多数 \(splay\) 能做的他都能做。

splay

\(splay\) 的核心在于其双旋操作,使其不依赖于随机值。

在每次操作后,将该点进行双旋转到根节点的位置,从而使复杂度达到均摊单次 \(O(\log(n))\) ,其复杂度分析详见 oi-wiki

哨兵节点

即添加 \(-inf、inf\) 节点,为极小值和极大值,使其避免查找出界,会对一些操作产生需要 \(±1\) 的操作。

单旋操作

image

设 \(x\) 是 \(y\) 在 \(k\) 方向上的儿子.

旋转后 \(y\) 是 \(x\) 在 \(k\bigoplus 1\) 方向上的儿子。

\(x\) 在 \(k\bigoplus 1\) 方向上的儿子成为 \(y\) 在 \(k\) 方向上的儿子。

其余不变。

单旋操作
void rotate(int x)
{
    int y=t[x].ff,z=t[y].ff,k=fson(y,x);
    t[z].ch[fson(z,y)]=x;
    t[x].ff=z;
    t[y].ch[k]=t[x].ch[k^1];
    t[t[x].ch[k^1]].ff=y;
    t[x].ch[k^1]=y;
    t[y].ff=x;
    pushup(y),pushup(x);
}

splay 操作(双旋操作)

将点 \(x\) 旋转到 \(goal\) 的子节点位置。

定义 \(fa_x=y,fa_y=z\) ,当 \(x、y、z\) 满足在同一条链时,先旋转 \(y\) ,再旋转 \(x\) ,使其满足平衡。

如图:

imageimageimage

将 \(x\) 不停进行单旋操作,左图中最大深度为 \(4\) ,右图最大深度仍为 \(4\) ,随意其实没有意义的。

对于上面所说的情况,如果先旋转 \(y\) ,再旋转 \(x\) ,最后为:

image

从而达到平衡。

splay 操作
void splay(int x,int goal)
{
    while(t[x].ff!=goal)
    {
        int y=t[x].ff,z=t[y].ff;
        if(z!=goal) 
            fson(z,y)^fson(y,x)?rotate(x):rotate(y);
        rotate(x);
    }
    if(goal==0) root=x;
}

另外为了方便,有一个 \(find\) 操作,即找到某点位置,再将其转到根节点。

find 操作
void find(int x)
{
    int p=root;
    if(!p) return ;
    while(f.ch[x>f.val]&&x!=f.val) p=f.ch[x>f.val];
    splay(p,0);
}

插入操作

找到离 \(x\) 最近的点,若该点权值就是 \(x\) ,则 \(cnt++\) ,否则建一个新的点,最后都要将其旋转到根节点。

插入操作
void insert(int x)
{
    int p=root,ff=0;
    while(p&&f.val!=x)
        ff=p,
        p=f.ch[x>f.val];
    if(p) {f.cnt++; splay(p,0); return ;}
    p=++tot;
    if(ff) t[ff].ch[x>t[ff].val]=p;
    f.ff=ff,f.val=x,f.size=f.cnt=1;
    splay(p,0);
}

删除操作

删除操作需要先找到其前驱与后继对应的位置,将前驱转到根节点,再将后继转到前驱的右儿子位置,那么此时他们俩中间夹着的,即后继的左儿子,即为 \(x\) 的位置,删除即可。

删除操作
void remove(int x)
{
    int pre=pre_nxt(x,0),nxt=pre_nxt(x,1);
    splay(pre,0),splay(nxt,pre);
    int p=t[nxt].l;
    if(f.cnt>1) f.cnt--,splay(p,0);
    else t[nxt].l=0;
}

查询前驱、后继

以前驱为例。

先找到离 \(x\) 最近的位置,如果该点权值就是 \(x\) ,先到其左儿子,然后向右不断地找,即为所求。

如果该点值不是 \(x\) ,若其恰好满足比 \(x\) 小,则该点即为所求,否则和上述一样即可。

注意此处找到的是其前驱的位置,不是数值,为了方便删除操作。

查询前驱、后继
int pre_nxt(int x,bool d)
{
    find(x);
    int p=root;
    if((f.val>x&&d)||(f.val<x&&!d)) return p;
    p=f.ch[d];
    while(f.ch[d^1]) p=f.ch[d^1];
    return p;
}

查询排名

此时需要满足 \(x\) 一定在树内,所以可以先插入,待查询后再删除。

先找到 \(x\) 所在位置,将其转到根节点位置,则此时其左儿子个数 \(+1\) 即为所求。

由于添加了哨兵节点,实际还需要 \(-1\) ,所以 \(ls.size\) 即为所求。

查询排名
int ask_rk(int x)
{
    find(x);
    int p=root;
    return ls.size;
}

查询第 \(k\) 小(大)值

与 \(Treap\) 一致。

优点

因为其 \(splay\) 操作,所以其能够进行维护序列等各种操作,完成所有 \(fhq\) 能支持甚至不支持的各种操作,但是常熟更大,码量更长,不如 \(fhq\) 好理解。

例题

基本操作

前三道为最基本的查询前驱后继、第 \(k\) 大值等问题。

luogu P2234 营业额统计

luogu P2286 宠物收养场

luogu P1486 郁闷的出纳员

luogu P3369 普通平衡树

Treap
#include<bits/stdc++.h>
// #define int long long 
#define endl '\n'
#define sort stable_sort
#define f t[p]
#define ls t[t[p].ch[0]]
#define rs t[t[p].ch[1]]
#define l ch[0]
#define r ch[1]
using namespace std;
const int N=1e5+10,inf=0x7f7f7f7f;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,tot,root;
struct treap
{
    int ch[2],cnt,size,val,dat;
}t[N];
void pushup(int p)
{
    f.size=ls.size+rs.size+f.cnt;
}
void rotate(int &p,bool d)
{
    int son=f.ch[d];
    f.ch[d]=t[son].ch[d^1];
    t[son].ch[d^1]=p;
    pushup(p);
    pushup(p=son);
}
void insert(int &p,int x)
{
    if(p==0)
    {
        p=++tot;
        f.cnt=f.size=1;
        f.val=x;
        f.dat=rand();
        return ;
    }
    f.size++;
    if(f.val==x) 
    {
        f.cnt++;
        return ;
    }
    bool d=f.val<x;
    insert(f.ch[d],x);
    if(f.dat>t[f.ch[d]].dat) rotate(p,d);
}
void remove(int &p,int x)
{
    if(p==0) return ;
    if(f.val==x)
    {
        if(f.cnt>1)
        {
            f.cnt--;
            f.size--;
            return ;
        }
        bool d=ls.dat>rs.dat;
        if(f.l==0||f.r==0) p=f.l+f.r;
        else rotate(p,d),remove(p,x);
    }
    else f.size--,remove(f.ch[f.val<x],x);
}
int ask_rk(int p,int x)
{
    if(p==0) return 1;
    if(f.val==x) return ls.size+1;
    if(f.val>x) return ask_rk(f.l,x);
    return ask_rk(f.r,x)+ls.size+f.cnt;
}
int ask_val(int p,int x)
{
    if(p==0) return inf;
    if(ls.size>=x) return ask_val(f.l,x);
    if(ls.size+f.cnt>=x) return f.val;
    return ask_val(f.r,x-ls.size-f.cnt);
}
int pre(int p,int x)
{
    if(p==0) return -inf;
    if(f.val>=x) return pre(f.l,x);
    return max(pre(f.r,x),f.val);
}
int nxt(int p,int x)
{
    if(p==0) return inf;
    if(f.val<=x) return nxt(f.r,x);
    return min(nxt(f.l,x),f.val);
}
signed main()
{
	#ifndef ONLINE_JUDGE
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif
    read(n);
    while(n--)
    {
        int op,x;
        read(op),read(x);
        switch(op)
        {
            case 1: insert(root,x); break;
            case 2: remove(root,x); break;
            case 3: write(ask_rk(root,x)),puts(""); break;
            case 4: write(ask_val(root,x)),puts(""); break;
            case 5: write(pre(root,x)),puts(""); break;
            case 6: write(nxt(root,x)),puts(""); break;
        }
    }
}
fhq_Treap
#include<bits/stdc++.h>
// #define int long long 
#define endl '\n'
#define sort stable_sort
#define f t[p]
#define ls t[t[p].ch[0]]
#define rs t[t[p].ch[1]]
#define l ch[0]
#define r ch[1]
using namespace std;
const int N=1e5+10,inf=0x7f7f7f7f;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,root,tot;
struct fhq_treap
{
    int ch[2],val,dat,size;
}t[N];
void pushup(int p) {f.size=ls.size+rs.size+1;}
void split(int p,int k,int &x,int &y)
{
    if(!p) {x=y=0; return ;}
    if(f.val>k) y=p,split(f.l,k,x,t[y].l);
    else x=p,split(f.r,k,t[x].r,y);
    pushup(p);
}
int merge(int x,int y)
{
    if(!x||!y) return x+y;
    int p;
    if(t[x].dat>t[y].dat)t[x].r=merge(t[x].r,y),pushup(p=x);
    else t[y].l=merge(x,t[y].l),pushup(p=y);
    return p;
}
void insert(int &p,int k)
{
    t[++tot].val=k,t[tot].size=1,t[tot].dat=rand();
    int x,y;
    split(p,k-1,x,y);
    p=merge(merge(x,tot),y);
}
void remove(int &p,int k)
{
    int x,y,is;
    split(p,k-1,x,y),split(y,k,is,y);
    p=merge(merge(x,merge(t[is].l,t[is].r)),y);
}
int ask_rk(int p,int k)
{
    int x,y,is;
    split(p,k-1,x,y);
    is=t[x].size+1;
    p=merge(x,y);
    return is;
}
int ask_val(int p,int k)
{
    if(!p) return inf;
    if(ls.size>=k) return ask_val(f.l,k);
    if(ls.size+1>=k) return f.val;
    return ask_val(f.r,k-ls.size-1);
}
int pre(int p,int k)
{
    if(!p) return -inf;
    if(f.val>=k) return pre(f.l,k);
    return max(pre(f.r,k),f.val);
}
int nxt(int p,int k)
{
    if(!p) return inf;
    if(f.val<=k) return nxt(f.r,k);
    return min(nxt(f.l,k),f.val);
}
signed main()
{
    read(n);
    for(int i=1,op,x;i<=n;i++)
    {
        read(op),read(x);
        switch(op)
        {
            case 1: insert(root,x); break;
            case 2: remove(root,x); break;
            case 3: write(ask_rk(root,x)),puts(""); break;
            case 4: write(ask_val(root,x)),puts(""); break;
            case 5: write(pre(root,x)),puts(""); break;
            case 6: write(nxt(root,x)),puts(""); break;
        }
    }
}
splay
#include<bits/stdc++.h>
// #define int long long 
#define endl '\n'
#define sort stable_sort
#define f t[p]
#define ls t[t[p].ch[0]]
#define rs t[t[p].ch[1]]
#define l ch[0]
#define r ch[1]
using namespace std;
const int N=1e5+10,inf=0x7f7f7f7f;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,root,tot;
struct splay
{
    int ch[2],ff,size,cnt,val;
}t[N];
bool fson(int x,int y) {return (t[x].ch[1]==y);}
void pushup(int p) {f.size=ls.size+rs.size+f.cnt;}
void rotate(int x)
{
    int y=t[x].ff,z=t[y].ff,k=fson(y,x);
    t[z].ch[fson(z,y)]=x;
    t[x].ff=z;
    t[y].ch[k]=t[x].ch[k^1];
    t[t[x].ch[k^1]].ff=y;
    t[x].ch[k^1]=y;
    t[y].ff=x;
    pushup(y),pushup(x);
}
void splay(int x,int goal)
{
    while(t[x].ff!=goal)
    {
        int y=t[x].ff,z=t[y].ff;
        if(z!=goal) 
            fson(z,y)^fson(y,x)?rotate(x):rotate(y);
        rotate(x);
    }
    if(goal==0) root=x;
}
void find(int x)
{
    int p=root;
    if(!p) return ;
    while(f.ch[x>f.val]&&x!=f.val) p=f.ch[x>f.val];
    splay(p,0);
}
void insert(int x)
{
    int p=root,ff=0;
    while(p&&f.val!=x)
        ff=p,
        p=f.ch[x>f.val];
    if(p) {f.cnt++; splay(p,0); return ;}
    p=++tot;
    if(ff) t[ff].ch[x>t[ff].val]=p;
    f.ff=ff,f.val=x,f.size=f.cnt=1;
    splay(p,0);
}
int pre_nxt(int x,bool d)
{
    find(x);
    int p=root;
    if((f.val>x&&d)||(f.val<x&&!d)) return p;
    p=f.ch[d];
    while(f.ch[d^1]) p=f.ch[d^1];
    return p;
}
void remove(int x)
{
    int pre=pre_nxt(x,0),nxt=pre_nxt(x,1);
    splay(pre,0),splay(nxt,pre);
    int p=t[nxt].l;
    if(f.cnt>1) f.cnt--,splay(p,0);
    else t[nxt].l=0;
}
int ask_rk(int x)
{
    find(x);
    int p=root;
    return ls.size;
}
int ask_val(int p,int x)
{
    if(!p) return inf;
    if(ls.size>=x) return ask_val(f.l,x);
    if(ls.size+f.cnt>=x) return f.val;
    return ask_val(f.r,x-ls.size-f.cnt);
}
signed main()
{
    insert(-inf),insert(inf);
    read(n);
    for(int i=1,op,x;i<=n;i++)
    {
        read(op),read(x);
        switch(op)
        {
            case 1: insert(x); break;
            case 2: remove(x); break;
            case 3: 
                insert(x);
                write(ask_rk(x)),puts(""); 
                remove(x);
                break;
            case 4: write(ask_val(root,x+1)),puts(""); break;
            case 5: write(t[pre_nxt(x,0)].val),puts(""); break;
            case 6: write(t[pre_nxt(x,1)].val),puts(""); break;
        }
    }
}

维护序列操作

luogu P3391 文艺平衡树

利用平衡树实现维护序列操作,可以使用 \(fhq\) 或 \(splay\) 。

  • \(fhq\)

    对于每次旋转,将其按照树的大小分裂,先按照 \(r\) 分裂, 再将左半部分按照 \(l-1\) 分裂,则中间一部分即为所求区间,向下打标记即可。

    翻转即左右儿子互换。

    最后中序遍历输出。

    文艺平衡树(fhq)
    #include<bits/stdc++.h>
    // #define int long long 
    #define endl '\n'
    #define sort stable_sort
    #define f t[p]
    #define ls t[t[p].ch[0]]
    #define rs t[t[p].ch[1]]
    #define l ch[0]
    #define r ch[1]
    using namespace std;
    const int N=1e5+10,inf=0x7f7f7f7f;
    template<typename Tp> inline void read(Tp&x)
    {
        x=0;register bool z=true;
        register char c=getchar();
        for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
        for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
        x=(z?x:~x+1);
    }
    void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
    void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
    int n,m,tot,root;
    struct fhq_treap
    {
        int ch[2],size,dat,val;
        bool add;
    }t[N];
    void pushup(int p) {f.size=ls.size+rs.size+1;}
    void spread(int p)
    {
        if(!f.add||!p) return ;
        swap(f.l,f.r);
        ls.add^=1,rs.add^=1;
        f.add=0;
    }
    void split(int p,int k,int &x,int &y)
    {
        if(!p) {x=y=0; return ;}
        spread(p);
        if(ls.size>=k) y=p,split(f.l,k,x,t[y].l);
        else x=p,split(f.r,k-ls.size-1,t[x].r,y);
        pushup(p);
    }
    int merge(int x,int y)
    {
        if(!x||!y) return x+y;
        spread(x),spread(y);
        int p;
        if(t[x].dat>t[y].dat) t[x].r=merge(t[x].r,y),pushup(p=x);
        else t[y].l=merge(x,t[y].l),pushup(p=y);
        return p;
    }
    void insert(int &p,int k)
    {
        t[++tot].val=k,t[tot].size=1,t[tot].dat=rand();
        p=merge(p,tot);
    }
    void solve(int &p,int ll,int rr)
    {
        int x,y,is;
        split(p,rr,x,y),split(x,ll-1,x,is);
        t[is].add^=1;
        p=merge(merge(x,is),y);
    }
    void dfs(int p)
    {
        if(!p) return ;
        spread(p);
        dfs(f.l);
        write(f.val),putchar(' ');
        dfs(f.r);
    }
    signed main()
    {
        read(n),read(m);
        for(int i=1;i<=n;i++)
            insert(root,i);
        for(int i=1,ll,rr;i<=m;i++)
            read(ll),read(rr),
            solve(root,ll,rr);
        dfs(root);
    }
    
  • \(splay\)

    找到 \(l-1\) 的位置,定义为 \(x\) ,\(r+1\) 的位置,定义为 \(y\) ,将 \(x\) 转到根节点,再将 \(y\) 转为 \(x\) 的子节点,那么根节点的右儿子的左儿子即对应所求区间,其余操作与 \(fhq\) 一致。

    文艺平衡树(splay)
    #include<bits/stdc++.h>
    // #define int long long 
    #define endl '\n'
    #define sort stable_sort
    #define f t[p]
    #define ls t[t[p].ch[0]]
    #define rs t[t[p].ch[1]]
    #define l ch[0]
    #define r ch[1]
    using namespace std;
    const int N=1e5+10,inf=0x7f7f7f7f;
    template<typename Tp> inline void read(Tp&x)
    {
        x=0;register bool z=true;
        register char c=getchar();
        for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
        for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
        x=(z?x:~x+1);
    }
    void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
    void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
    int n,m,root,tot;
    struct splay
    {
        int ch[2],ff,size,cnt,val;
        bool add;
    }t[N];
    bool fson(int x,int y) {return t[x].ch[1]==y;}
    void pushup(int p) {f.size=ls.size+rs.size+f.cnt;}
    void spread(int p)
    {
        if(!p||!f.add) return ;
        swap(f.l,f.r);
        ls.add^=1,rs.add^=1;
        f.add=0;
    }
    void rotate(int x)
    {
        int y=t[x].ff,z=t[y].ff,k=fson(y,x);
        t[z].ch[fson(z,y)]=x;
        t[x].ff=z;
        t[y].ch[k]=t[x].ch[k^1];
        t[t[x].ch[k^1]].ff=y;
        t[x].ch[k^1]=y;
        t[y].ff=x;
        pushup(y),pushup(x);
    }
    void splay(int x,int goal)
    {
        while(t[x].ff!=goal)
        {
            int y=t[x].ff,z=t[y].ff;
            if(z!=goal) 
                fson(z,y)^fson(y,x)?rotate(x):rotate(y);
            rotate(x);
        }
        if(goal==0) root=x;
    }
    void insert(int x)
    {
        int p=root,ff=0;
        while(p&&f.val!=x)
            ff=p,
            p=f.ch[x>f.val];
        if(p) {f.cnt++; splay(p,0); return ;}
        p=++tot;
        if(ff) t[ff].ch[x>t[ff].val]=p;
        f.ff=ff,f.val=x,f.size=f.cnt=1;
        splay(p,0);
    }
    int find(int p,int x)
    {
        spread(p);
        if(ls.size>=x) return find(f.l,x);
        if(ls.size+f.cnt>=x) return p;
        return find(f.r,x-ls.size-f.cnt);
    }
    void solve(int x,int y)
    {
        x=find(root,x),y=find(root,y);
        splay(x,0),splay(y,x);
        t[t[t[root].r].l].add^=1;
    }
    void dfs(int p)
    {
        if(!p) return ;
        spread(p);
        dfs(f.l);
        if(f.val!=1&&f.val!=n+2) write(f.val-1),putchar(' ');
        dfs(f.r);
    }
    signed main()
    {
        read(n),read(m);
        for(int i=1;i<=n+2;i++) insert(i);
        for(int i=1,ll,rr;i<=m;i++)
            read(ll),read(rr),
            solve(ll,rr+2);
        dfs(root);
    }
    

luogu P3165 排序机械臂

因为其不会插入新点,所以可以通过排序得知第 \(k\) 小值的编号。

对于查询其所在位置,利用 \(splay\) 将其转到根节点,则根节点的左儿子的 \(size+1\) 即为所求。

区间翻转操作与文艺平衡树类似,同时向树中存的应为下标而不是权值。

注意审题与哨兵节点插入的顺序。

机械臂排序
#include<bits/stdc++.h>
// #define int long long 
#define endl '\n'
#define sort stable_sort
#define f t[p]
#define ls t[t[p].ch[0]]
#define rs t[t[p].ch[1]]
#define l ch[0]
#define r ch[1]
using namespace std;
const int N=1e5+10,inf=0x7f7f7f7f;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,root,tot,a[N];
struct aa {int h,id;}e[N];
bool cmp(aa a,aa b) {return a.h==b.h?a.id<b.id:a.h<b.h;}
struct splay
{
    int ch[2],ff,size,cnt,val;
    bool add;
}t[N];
bool fson(int x,int y) {return t[x].ch[1]==y;}
void pushup(int p) {f.size=ls.size+rs.size+f.cnt;}
void spread(int p)
{
    if(!p||!f.add) return ;
    swap(f.l,f.r);
    ls.add^=1,rs.add^=1;
    f.add=0;
}
void rotate(int x)
{
    int y=t[x].ff,z=t[y].ff,k=fson(y,x);
    t[z].ch[fson(z,y)]=x;
    t[x].ff=z;
    t[y].ch[k]=t[x].ch[k^1];
    t[t[x].ch[k^1]].ff=y;
    t[x].ch[k^1]=y;
    t[y].ff=x;
    pushup(y),pushup(x);
}
void splay(int x,int goal)
{
    while(t[x].ff!=goal)
    {
        int y=t[x].ff,z=t[y].ff;
        spread(z),spread(y),spread(x);
        if(z!=goal) 
            fson(z,y)^fson(y,x)?rotate(x):rotate(y);
        rotate(x);
    }
    if(goal==0) root=x;
}
void insert(int x)
{
    int p=root,ff=0;
    while(p&&f.val!=x)
        ff=p,
        p=f.ch[x>f.val];
    if(p) {f.cnt++; splay(p,0); return ;}
    p=++tot;
    if(ff) t[ff].ch[x>t[ff].val]=p;
    f.ff=ff,f.val=x,f.size=f.cnt=1;
    splay(p,0);
}
int find(int p,int x)
{
    spread(p);
    if(ls.size>=x) return find(f.l,x);
    if(ls.size+f.cnt>=x) return p;
    return find(f.r,x-ls.size-f.cnt);
}
void solve(int x,int y)
{
    x--,y++;
    x=find(root,x),y=find(root,y);
    splay(x,0),splay(y,x);
    t[t[t[root].r].l].add^=1;
}
int ask(int x)
{
    splay(x,0);
    return t[t[root].l].size+1;
}
void solve(int i)
{
    int x=i,y=ask(e[i].id);
    solve(x,y);
}
signed main()
{
    srand(time(0));
    insert(1);
    read(n);
    for(int i=2;i<=n+1;i++)
        read(e[i].h),
        e[i].id=i,
        insert(i);
    insert(n+2);
    sort(e+2,e+2+n,cmp);
    for(int i=2;i<=n+1;i++)
        write(ask(e[i].id)-1),putchar(' '),
        solve(i);
}

luogu P4309 最长上升子序列

回想最长上升子序列的 \(DP\) 式子,\(dp_i=\max\limits_{0<j<i\&\&a_j<a_i}\{dp_j\}+1\) 。

此处由于每次插入的数一定大于之前插入的所有数,所以直接在 \(0<j<i\) 中找到最大的 \(dp_j+1\) 即为 \(dp_i\) 。

因为其需要动态插入,可以离线后线段树维护,也可以平衡树动态维护。

对于没一个节点定义 \(val\) 表示其子树中最大的 \(dp\) 值,\(dp\) 表示其自身的 \(dp\) 值,由此将子树的信息传递给根节点以便查询。

void pushup(int p) 
{
    f.val=max({ls.val,rs.val,f.dp});
}

插入操作可以用 \(fhq\) 或 \(splay\) 支持。

最长上升子序列
#include<bits/stdc++.h>
// #define int long long 
#define endl '\n'
#define sort stable_sort
#define f t[p]
#define ls t[t[p].ch[0]]
#define rs t[t[p].ch[1]]
#define l ch[0]
#define r ch[1]
using namespace std;
const int N=1e5+10,inf=0x7f7f7f7f;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,root,tot;
struct fhq_treap
{
    int ch[2],size,dat,ans,len;
}t[N];
void pushup(int p) 
{
    f.size=ls.size+rs.size+1;
    f.ans=max({ls.ans,rs.ans,f.len});
}
void split(int p,int k,int &x,int &y)
{
    if(!p) {x=y=0; return ;}
    if(ls.size>=k) y=p,split(f.l,k,x,t[y].l);
    else x=p,split(f.r,k-ls.size-1,t[x].r,y);
    pushup(p);
}
int merge(int x,int y)
{
    if(!x||!y) return x+y;
    int p;
    if(t[x].dat>t[y].dat) t[x].r=merge(t[x].r,y),pushup(p=x);
    else t[y].l=merge(x,t[y].l),pushup(p=y);
    return p;
}
void insert(int &p,int k)
{
    t[++tot].size=1,t[tot].dat=rand();
    int x,y;
    split(p,k,x,y);
    t[tot].len=t[tot].ans=t[x].ans+1;
    p=merge(merge(x,tot),y);
}
signed main()
{
    srand(time(0));
    read(n);
    for(int i=1,x;i<=n;i++)
        read(x),
        insert(root,x),
        write(t[root].ans),puts("");
}

树上哈希

对于每个节点,保存的是其子树的 \(hash\) 值。

void pushup(int p) 
{
    f.hash=ls.hash*b[rs.size+1]+f.val*b[rs.size]+rs.hash;
}

luogu P4036 火星人

利用树上哈希,对于每次询问,二分答案即可。

查询一个区间的 \(hash\) 询问,与文艺平衡树类似的,找到这个区间,此时的 \(hash\) 值即为所求。

火星人
#include<bits/stdc++.h>
// #define int long long 
#define ull unsigned long long 
#define endl '\n'
#define sort stable_sort
#define f t[p]
#define ls t[t[p].ch[0]]
#define rs t[t[p].ch[1]]
#define l ch[0]
#define r ch[1]
using namespace std;
const int N=3e5+10,inf=0x7f7f7f7f;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,m,tot,root;
ull b[N];
char s[N];
struct fhq_treap
{
    int ch[2],size,dat;
    ull val,hash;
}t[N];
void pushup(int p) 
{
    f.size=ls.size+rs.size+1;
    f.hash=ls.hash*b[rs.size+1]+f.val*b[rs.size]+rs.hash;
}
void split(int p,int k,int &x,int &y)
{
    if(!p) {x=y=0; return ;}
    if(ls.size>=k) y=p,split(f.l,k,x,t[y].l);
    else x=p,split(f.r,k-ls.size-1,t[x].r,y);
    pushup(p);
}
int merge(int x,int y)
{
    if(!x||!y) return x+y;
    int p;
    if(t[x].dat>t[y].dat) t[x].r=merge(t[x].r,y),pushup(p=x);
    else t[y].l=merge(x,t[y].l),pushup(p=y);
    return p;
}
void insert(int &p,int k,int d)
{
    t[++tot].val=d,t[tot].size=1,t[tot].dat=rand(),t[tot].hash=d;
    int x,y;
    split(p,k,x,y);
    p=merge(merge(x,tot),y);
}
void change(int &p,int k,int d)
{
    t[++tot].val=d,t[tot].size=1,t[tot].dat=rand(),t[tot].hash=d;
    int x,y,is;
    split(p,k,x,y),split(x,k-1,x,is);
    p=merge(merge(x,tot),y);
}
int ask(int p,int ll,int rr)
{
    int x,y,is;
    ull ans;
    split(p,rr,x,y),split(x,ll-1,x,is);
    ans=t[is].hash;
    p=merge(merge(x,is),y);
    return ans;
}
bool check(int x,int y,int mid)
{
    return ask(root,x,x+mid-1)==ask(root,y,y+mid-1);
}
int ask(int x,int y)
{
    int ll=0,rr=min(n-x+1,n-y+1),mid,ans=0;
    while(ll<=rr)
    {
        mid=(ll+rr)>>1;
        if(check(x,y,mid)) ans=mid,ll=mid+1;
        else rr=mid-1;
    }
    return ans;
}
void pre()
{
    b[0]=1,b[1]=233;
    for(int i=2;i<=N-1;i++) b[i]=b[i-1]*b[1];
}
signed main()
{
    srand(time(0));
    cin>>s+1; read(m);
    n=strlen(s+1);
    pre();
    for(int i=1;i<=n;i++)
        insert(root,i-1,s[i]-'a'+1);
    while(m--)
    {
        char op,d; int x,y;
        cin>>op;
        if(op=='Q') 
        {
            read(x),read(y);
            if(x>y) swap(x,y);
            write(ask(x,y)),puts("");
        }
        if(op=='R')
            read(x),cin>>d,
            change(root,x,d-'a'+1);
        if(op=='I')
            read(x),cin>>d,
            insert(root,x,d-'a'+1),
            n++;
    }
}

AtCoder abc331_f Palindrome Query

类似的二分答案即可,需要存正串和反串,同时存在线段树解法,不过多展开。

Palindrome Query
#include<bits/stdc++.h>
// #define int long long 
#define ull unsigned long long 
#define endl '\n'
#define sort stable_sort
#define f t[p]
#define ls t[t[p].ch[0]]
#define rs t[t[p].ch[1]]
#define l ch[0]
#define r ch[1]
using namespace std;
const int N=2e6+10,inf=0x7f7f7f7f;
template<typename Tp> inline void read(Tp&x)
{
    x=0;register bool z=true;
    register char c=getchar();
    for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
    for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
    x=(z?x:~x+1);
}
void wt(int x){if(x>9)wt(x/10);putchar((x%10)+'0');}
void write(int x){if(x<0)putchar('-'),x=~x+1;wt(x);}
int n,m,root,tot;
ull b[N];
char s[N];
struct fhq_treap
{
    int ch[2],val,size,dat;
    ull hash[2];
}t[N];
void pushup(int p)
{
    f.size=ls.size+rs.size+1;
    f.hash[0]=ls.hash[0]*b[rs.size+1]+f.val*b[rs.size]+rs.hash[0];
    f.hash[1]=rs.hash[1]*b[ls.size+1]+f.val*b[ls.size]+ls.hash[1];
}
void split(int p,int k,int &x,int &y)
{
    if(!p) {x=y=0; return ;}
    if(ls.size>=k) y=p,split(f.l,k,x,t[y].l);
    else x=p,split(f.r,k-ls.size-1,t[x].r,y);
    pushup(p);
}
int merge(int x,int y)
{
    if(!x||!y) return x+y;
    int p;
    if(t[x].dat>t[y].dat) t[x].r=merge(t[x].r,y),pushup(p=x);
    else t[y].l=merge(x,t[y].l),pushup(p=y);
    return p;
}
void insert(int &p,int k,int d)
{
    t[++tot].val=d,t[tot].size=1,t[tot].dat=rand(),t[tot].hash[0]=t[tot].hash[1]=d;
    int x,y;
    split(p,k,x,y);
    p=merge(merge(x,tot),y);
}
void change(int &p,int k,int d)
{
    t[++tot].val=d,t[tot].size=1,t[tot].dat=rand(),t[tot].hash[0]=t[tot].hash[1]=d;
    int x,y,is;
    split(p,k,x,y),split(x,k-1,x,is);
    p=merge(merge(x,tot),y);
}
int ask(int p,int ll,int rr,bool d)
{
    int x,y,is;
    ull ans;
    split(p,rr,x,y),split(x,ll-1,x,is);
    ans=t[is].hash[d];
    p=merge(merge(x,is),y);
    return ans;
}
void ask(int ll,int rr)
{
    if(ll==rr) {puts("Yes"); return ;}
    int mid=(rr-ll+1)>>1;
    puts(ask(root,ll,ll+mid-1,0)==ask(root,rr-mid+1,rr,1)?"Yes":"No");
}
void pre()
{
    b[0]=1,b[1]=233;
    for(int i=2;i<=N-1;i++) b[i]=b[i-1]*b[1];
}
signed main()
{
    srand(time(0));
    read(n),read(m);
    pre();
    cin>>s+1;
    for(int i=1;i<=n;i++) insert(root,i-1,s[i]-'a'+1);
    while(m--)
    {
        int op,x,y; char d;
        read(op);
        if(op==1) read(x),cin>>d,change(root,x,d-'a'+1);
        else read(x),read(y),ask(x,y);
    }
}

标签:ch,return,val,int,笔记,学习,平衡,size,define
From: https://www.cnblogs.com/Charlieljk/p/18212454

相关文章

  • Vue3实战笔记(46)—Vue 3高效开发定制化Dashboard的权威手册
    文章目录前言Dashboard开发总结前言后台管理系统中的Dashboard是一种图形化的信息显示工具,通常用于提供一个特定领域或系统的概况。它可以帮助用户监控和分析数据,快速获取重要信息。可以帮助用户监控业务状况、分析数据、获取关键信息和管理资源。通过合理的设计和......
  • Vue3实战笔记(47)— 一探emit奥秘——组件间通信的艺术与实践
    文章目录前言一、Vue2中的emti二、Vue3的emit总结前言Vue封装了自定义组件之后,如果子组件想要向父组件传递数据该怎么办?Vue.js中的emit方法就是主要用于组件间的通信,特别是父组件与子组件之间的通信机制。它是Vue组件内部触发自定义事件并向父级组件传递数......
  • Java反射(个人学习笔记)
    Java反射反射的定义:Java反射是指在运行时动态地获取类的信息,并可以通过该信息来操作类或对象。通过反射,我们可以在运行时获取类的字段、方法、构造函数等信息,并能够动态地创建对象、调用方法、访问和修改字段的值。反射相关的类:Class类代表类的实体,在运行的Java应用程......
  • 强化学习快速入门
    本文章通过强化学习快速入门(https://zhuanlan.zhihu.com/p/699934259)在线发布并更新。1.强化学习直观理解强化学习的应用场景是马尔可夫过程,很多现实中的问题都可以认为是马尔可夫过程,特征是当前状态仅仅与前一个状态有关,而与更早的状态无关。按照随机过程的定义:\[\begin{ali......
  • Docker学习笔记
    1Docker简介1.1为什么会有Docker问题在实际开发过程中,会出现很多环境:开发环境、测试环境以及生产环境。那么我们如何解决这个“水土不服”的问题?我们可以将软件带环境安装,来解决这种问题。解决带环境安装。软件可以带环境安装?也就是说,开发人员要交付的是代码和环境......
  • 深度强化学习在芯片设计和大语言模型中的应用
    这个研究课题来自ANNAGOLDIE,我进行了总结,目前最前沿的深度深度强化学习在芯片设计和大语言模型中的应用技术。(2024年)ANNAGOLDIE,aSeniorStaffResearchScientistatGoogleDeepMind,wheresheworksonGeminiandBard.我将这部分内容整理成书籍,目前先放出核心的......
  • Stanford斯坦福 CS 224R: 深度强化学习 (7)
    多任务和目标条件强化学习第一章引言1.1多任务学习的动机在之前的课程中,我们学习了强化学习的基本概念和算法,如模仿学习、策略梯度、Q学习等。然而,这些方法在实际应用中往往面临着样本效率低下的挑战。收集大量高质量的互动数据是昂贵且耗时的,特别是对于复杂的决策......
  • 【Linux学习】进程间通信 (2) —— 信号
    下面是有关进程通信中信号的相关介绍,希望对你有所帮助!小海编程心语录-CSDN博客目录1.信号 1.1概念 1.2信号的产生 1.3信号的处理方式 2.函数 2.1kill()函数 2.2 signal()函数 2.3 sigaction()函数 2.4 sigprocmask()函数 2.5sigqueue()函数......
  • Stanford斯坦福 CS 224R: 深度强化学习 (6)
    CS224R离线强化学习:第二部分课程介绍请看第一节内容课程回顾离线强化学习、数据约束和保守性离线强化学习旨在利用离线数据,重复使用离线数据是有益的。其关键挑战是由于πβ......
  • VOOM 笔记
    原始题目VOOM:RobustVisualObjectOdometryandMappingusingHierarchicalLandmarks中文名称使用分层landmarks的Robust视觉目标里程计和建图发表时间2024年2月21日平台ICRA2024来源北京理工大学文章链接https://arxiv.org/abs/2402.136......