平衡树 学习笔记

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 P3380 树套树

可以有多种实现方法,此处采用思路较易的线段树维护区间,平衡树维护权值,缺点是查询第 \(k\) 小值需要 \(\log^3\) ,其余均是正常的 \(\log^2\) ,但因为常熟不是很大,并没有慢太多。

  • 建树与修改

    对于线段树上每一个节点开一棵平衡树,直接暴力插入,因为线段树共有 \(\log(n)\) 层,每一层元素数量均为 \(n\) ,每次插入复杂度为 \(O(\log(n))\) ,所以建树的复杂度为 \(O(n\log^2(n))\)

    修改即删除旧的,插入新的即可,修改后不要忘了另 \(a_i=new\)

  • 查询排名

    对于比 \(k\) 小的个数区间求和最后 \(+1\) 即可,复杂度 \(O(\log^2(n))\)

  • 查询第 \(k\) 小值

    考虑二分答案,比较该数的排名与 \(k\) 的大小,复杂度 \(O(\log^3(n))\)

  • 查询前驱、后继

    前驱则是线段树区间最大值,后继为区间最小值,当然都是针对线段树上每个节点对于平衡树上的前驱(后继)值,复杂度 \(O(\log^2(n))\)

树套树
#include<bits/stdc++.h>
// #define int long long 
#define endl '\n'
#define sort stable_sort
using namespace std;
const int N=8e6+10,inf=2147483647;
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,a[N],maxx=-inf,minn=inf;
struct fhq_treap
{
    #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]
    struct treap
    {
        int ch[2],cnt,size,val,dat;
    }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 leq(int &p,int k)
    {
        int x,y,is;
        split(p,k-1,x,y);
        is=t[x].size;
        p=merge(x,y);
        return is;
    }
    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);
    }
    #undef f 
    #undef ls 
    #undef rs 
    #undef l 
    #undef r 
}fhq;
#define f t[p]
#define ls p<<1
#define rs p<<1|1
struct aa {int l,r,root;}t[N];
void build(int p,int l,int r)
{
    f.l=l,f.r=r;
    for(int i=l;i<=r;i++)
        fhq.insert(f.root,a[i]);
    if(l==r) return ;
    int mid=(l+r)>>1;
    build(ls,l,mid),build(rs,mid+1,r);
}
void change(int p,int x,int d)
{
    fhq.remove(f.root,a[x]);
    fhq.insert(f.root,d);
    if(f.l==f.r) return ;
    int mid=(f.l+f.r)>>1;
    if(x<=mid) change(ls,x,d);
    else change(rs,x,d);
}
int leq(int p,int l,int r,int x)
{
    if(l<=f.l&&r>=f.r) return fhq.leq(f.root,x);
    int mid=(f.l+f.r)>>1,ans=0;
    if(l<=mid) ans+=leq(ls,l,r,x);
    if(r>mid) ans+=leq(rs,l,r,x);
    return ans;
}
int ask_rk(int l,int r,int x) {return leq(1,l,r,x)+1;}
int ask_val(int ll,int rr,int x) 
{
    int l=minn,r=maxx,mid,ans=inf;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(ask_rk(ll,rr,mid)<=x) ans=mid,l=mid+1;
        else r=mid-1;
    }
    return ans;
}
int pre(int p,int l,int r,int x)
{
    if(l<=f.l&&r>=f.r) return fhq.pre(f.root,x);
    int mid=(f.l+f.r)>>1,ans=-inf;
    if(l<=mid) ans=max(ans,pre(ls,l,r,x));
    if(r>mid) ans=max(ans,pre(rs,l,r,x));
    return ans;
}
int nxt(int p,int l,int r,int x)
{
    if(l<=f.l&&r>=f.r) return fhq.nxt(f.root,x);
    int mid=(f.l+f.r)>>1,ans=inf;
    if(l<=mid) ans=min(ans,nxt(ls,l,r,x));
    if(r>mid) ans=min(ans,nxt(rs,l,r,x));
    return ans;
}
#undef f 
#undef ls 
#undef rs 
signed main()
{
    read(n),read(m);
    for(int i=1;i<=n;i++) 
        read(a[i]),
        maxx=max(maxx,a[i]),minn=min(minn,a[i]);
    build(1,1,n);
    for(int i=1,op,l,r,x,k;i<=m;i++)
    {
        read(op);
        switch(op)
        {
            case 1: 
                read(l),read(r),read(k),
                write(ask_rk(l,r,k)),puts("");
                break;
            case 2:
                read(l),read(r),read(k),
                write(ask_val(l,r,k)),puts("");
                break;
            case 3:     
                read(x),read(k),
                maxx=max(maxx,k),minn=min(minn,k);
                change(1,x,k);
                a[x]=k;
                break;
            case 4:
                read(l),read(r),read(k),
                write(pre(1,l,r,k)),puts("");
                break;
            case 5:
                read(l),read(r),read(k),
                write(nxt(1,l,r,k)),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);
    }
}
posted @ 2024-05-28 21:13  卡布叻_周深  阅读(20)  评论(0编辑  收藏  举报