平衡树 学习笔记
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\) 基本一致。
优点
-
为所有平衡树中最好打,最容易理解的一种。
-
因为不需要旋转,所以可以可持久化。
-
允许有权值一样的不同节点,因为其是按照权值分裂的。
-
因为按照权值分裂,所以不在树中的数也可以直接查。
-
当其按照树的大小分裂时,可以实现维护序列,这是 \(Treap\) 做不到的,大多数 \(splay\) 能做的他都能做。
splay
\(splay\) 的核心在于其双旋操作,使其不依赖于随机值。
在每次操作后,将该点进行双旋转到根节点的位置,从而使复杂度达到均摊单次 \(O(\log(n))\) ,其复杂度分析详见 oi-wiki 。
哨兵节点
即添加 \(-inf、inf\) 节点,为极小值和极大值,使其避免查找出界,会对一些操作产生需要 \(±1\) 的操作。
单旋操作
设 \(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\) ,使其满足平衡。
如图:
将 \(x\) 不停进行单旋操作,左图中最大深度为 \(4\) ,右图最大深度仍为 \(4\) ,随意其实没有意义的。
对于上面所说的情况,如果先旋转 \(y\) ,再旋转 \(x\) ,最后为:
从而达到平衡。
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);
}
}