1. 非旋 Treap(FHQ Treap)

1.1. 算法简介

FHQ Treap 的功能非常强大。它涵盖了 Treap 几乎所有的功能 所以我非常后悔学了 Treap,浪费时间。

FHQ 的核心思想在于分裂和合并

我们所需要的存储的信息:左儿子 \(ls\),右儿子 \(rs\),权值 \(val\),子树大小 \(sz\) 以及 Treap 所特有的随机优先级 \(rd\)。一般情况下,FHQ Treap 将权值相同的节点看成多个节点,故不需要记录节点所存储的数的个数 \(cnt\)

1.1.1. 更新答案

基础中的基础。

void push(int x){sz[x]=sz[ls[x]]+sz[rs[x]]+1;}

1.1.2. 分裂

FHQ Treap:我裂开来。

有两种分裂方式,一种是按权值 \(val\) 分裂,另一种是按大小 \(sz\) 分裂。

  • 按权值分裂:具体地,给定 \(v\),我们要将原来的平衡树 \(T\) 分裂成两个平衡树 \(T_x\) 与 \(T_y\),满足对于任意 \(i\in T_x,\ j\in T_y\),都有 \(val_i\leq v<val_j\)。

    假设当前节点为 \(p\):如果 \(val_p\leq v\),那么 \(p\) 应属于 \(T_x\),而 \(p\) 的左儿子 \(ls_p\) (lsp) 显然也属于 \(T_x\),故只需要向右递归 \(rs_p\) 即可;反之则有 \(val_p>v\),那么 \(p\) 应属于 \(T_y\),而 \(p\) 的右儿子 \(rs_p\) 显然也属于 \(T_y\),故只需要向右递归 \(ls_p\) 即可。

    递归边界:\(p=0\)。

    代码实现如下:

void spl(int p,int v,int &x,int &y){
if(!p)return x=y=0,void();
if(val[p]<=v)spl(rs[p],v,rs[x=p],y);
else spl(ls[p],v,x,ls[y=p]);
push(p);
}
  • 按大小分裂:同样的,给定 \(v\),对于当前节点 \(p\),如果 \(v\leq sz_{ls_p}\),那么 \(p\) 和整个 \(rs_p\) 的子树都应被分到 \(T_y\),向 \(ls_p\) 递归即可。反之亦然。

    代码实现如下:

void spl(int p,int v,int &x,int &y){
if(!p)return x=y=0,void();
if(v<=sz[ls[p]])spl(ls[p],v,x,ls[y=p]);
else spl(rs[p],v-sz[ls[p]]-1,rs[x=p],y);
push(x);
}

1.1.3. 合并

像极了线段树分裂和合并。

首先,合并的两棵树 \(T_x\) 与 \(T_y\) 必须要满足对于任意 \(i\in T_x,\ j\in T_y\),都有 \(val_i<val_j\)。假设当前我们要合并 \(x,y\) 两个节点:

  • 如果 \(rd_x>rd_y\):那么 \(x\) 的优先级更高。因此 \(x\) 应当是 \(y\) 的父亲。而根据二叉平衡树的性质,因为 \(val_x<val_y\),所以 \(y\) 是 \(x\) 的右儿子。故直接调用 \(rs_x\gets \mathrm{merge}(rs_x,y)\) 即可。
  • 反之亦然,直接调用 \(ls_y\gets \mathrm{merge}(x,ls_y)\)。
  • 递归边界:\(x,y\) 中至少有一个为空。此时我们使用类似线段树合并的 Trick,直接返回 \(x\ \mathrm{or}\ y\) 即可。

代码实现:

int mer(int x,int y){
if(!x||!y)return x|y;
if(rd[x]<rd[y])return ls[y]=mer(x,ls[y]),push(y),y;
return rs[x]=mer(rs[x],y),push(x),x;
}

FHQ Treap 是真 tmd 好写!!!

1.1.4. 新建节点

int nd(int v){
int x=++node;
val[x]=v,rd[x]=rand(),sz[x]=1;
return x;
}

1.2. 其它功能

1.2.1. 插入

插入一个数 \(v\):只需要用 \(v-1\) 按照权值分裂成两棵树 \(T_x,T_y\),再 \(root=\mathrm{merge}(\mathrm{merge}(x,\mathrm{newnode}(v)),y)\) 即可。

void ins(int v){
int x=0,y=0;
spl(R,v-1,x,y),R=mer(mer(x,nd(v)),y);
}

1.2.2. 删除

删除一个数 \(v\):先用 \(v\) 按照权值分裂成两棵树 \(T_x,T_z\),再用 \(v-1\) 按照权值将 \(T_x\) 分裂成两棵树 \(T_x,T_y\)。此时 \(T_y\) 的所有节点的权值就都是 \(v\)。但是我们只能删一个,因此可以将 \(y\) 的左右节点合并起来,这样 \(T_y\) 的节点个数就少了一个。最后再合并回来即可。

void del(int v){
int x=0,y=0,z=0;
spl(R,v,x,z),spl(x,v-1,x,y);
R=mer(mer(x,y=mer(ls[y],rs[y])),z);
}

1.2.3. 第 k 大

查找第 \(k\) 大的数:假设当前遍历到节点 \(p\)。若 \(k\leq sz_{ls_p}\) 则向左儿子递归;若 \(k=sz_{ls_p}+1\) 则直接返回 \(val_p\);否则向右儿子递归。

int kth(int k){
int p=R;
while(1){
if(k<=sz[ls[p]])p=ls[p];
else if(k==sz[ls[p]])return val[p];
else k-=sz[ls[p]]+1,p=rs[p];
}
}

1.2.4. 前驱 / 后继

查找 \(v\) 的前驱:找一种方法是直接在 \(T\) 里面找,另一种是用 \(v-1\) 按照权值分裂成 \(T_x,T_y\),再找 \(T_x\) 的最大值即可(即 \(T_x\) 的第 \(sz_x\) 大值)。后继同理。

int pre(int v){
int p=R,ans=0;
while(1){
if(!p)return ans;
else if(v<=val[p])p=ls[p];
else ans=val[p],p=rs[p];
}
}
int suc(int v){
int p=R,ans=0;
while(1){
if(!p)return ans;
else if(v>=val[p])p=rs[p];
else ans=val[p],p=ls[p];
}
}

1.2.5. 排名

查询 \(v\) 的排名:用 \(v-1\) 按照权值分裂成 \(T_x,T_y\),那么答案即为 \(sz_x+1\)。最后合并。

int rnk(int v){
int x=0,y=0,ans;
spl(R,v-1,x,y),ans=sz[x]+1;
return R=mer(x,y),ans;
}

1.2.6. 区间翻转

平衡树维护区间,那么每个节点存储的就是对应位置的值,而它的中序遍历就是整个区间。

只需要将要翻转的区间分裂出来,打上标记再合并即可。注意标记下推交换左右儿子,具体见例题 II。

1.3. 例题

I. P3369 【模板】普通平衡树

板子题。

#include <bits/stdc++.h>
using namespace std; const int N=1e5+5;
const int inf=1e9+7; int R,node,ls[N],rs[N],val[N],rd[N],sz[N];
void push(int x){sz[x]=sz[ls[x]]+sz[rs[x]]+1;} void spl(int p,int v,int &x,int &y){
if(!p)return x=y=0,void();
if(val[p]<=v)spl(rs[p],v,rs[x=p],y);
else spl(ls[p],v,x,ls[y=p]);
push(p);
}
int mer(int x,int y){
if(!x||!y)return x|y;
if(rd[x]<rd[y])return ls[y]=mer(x,ls[y]),push(y),y;
return rs[x]=mer(rs[x],y),push(x),x;
}
int nd(int v){
int x=++node;
val[x]=v,rd[x]=rand(),sz[x]=1;
return x;
}
void ins(int v){
int x=0,y=0;
spl(R,v-1,x,y),R=mer(mer(x,nd(v)),y);
}
void del(int v){
int x=0,y=0,z=0;
spl(R,v,x,z),spl(x,v-1,x,y);
R=mer(mer(x,y=mer(ls[y],rs[y])),z);
}
int kth(int k){
int p=R;
while(1){
if(k<=sz[ls[p]])p=ls[p];
else if(k==sz[ls[p]]+1)return val[p];
else k-=sz[ls[p]]+1,p=rs[p];
}
}
int pre(int v){
int p=R,ans=0;
while(1){
if(!p)return ans;
else if(v<=val[p])p=ls[p];
else ans=val[p],p=rs[p];
}
}
int suc(int v){
int p=R,ans=0;
while(1){
if(!p)return ans;
else if(v>=val[p])p=rs[p];
else ans=val[p],p=ls[p];
}
}
int rnk(int v){
int x=0,y=0,ans=0;
spl(R,v-1,x,y),ans=sz[x]+1;
return R=mer(x,y),ans;
} int n,m;
int main(){
cin>>n;
for(int i=1;i<=n;i++){
int op,x; cin>>op>>x;
if(op==1)ins(x);
if(op==2)del(x);
if(op==3)cout<<rnk(x)<<endl;
if(op==4)cout<<kth(x)<<endl;
if(op==5)cout<<pre(x)<<endl;
if(op==6)cout<<suc(x)<<endl;
}
return 0;
}

II. P3391 【模板】文艺平衡树

这里给出代码,是真的短。

#include <bits/stdc++.h>
using namespace std; const int N=1e5+5; int R,node,ls[N],rs[N],tg[N],val[N],sz[N],rd[N]; void push(int x){sz[x]=sz[ls[x]]+sz[rs[x]]+1;}
void down(int x){if(tg[x])tg[ls[x]]^=1,tg[rs[x]]^=1,swap(ls[x],rs[x]),tg[x]=0;}
int nd(int v){int x=++node; return rd[x]=rand(),val[x]=v,sz[x]=1,x;} void spl(int p,int v,int &x,int &y){
if(!p)return x=y=0,void();
down(p);
if(sz[ls[p]]>=v)spl(ls[p],v,x,ls[y=p]);
else spl(rs[p],v-sz[ls[p]]-1,rs[x=p],y);
push(p);
}
int mer(int x,int y){
if(!x||!y)return x|y;
down(x),down(y);
if(rd[x]>rd[y])return rs[x]=mer(rs[x],y),push(x),x;
return ls[y]=mer(x,ls[y]),push(y),y;
} void modify(int l,int r){int x,y,z; spl(R,r,x,z),spl(x,l-1,x,y),tg[y]^=1,R=mer(x,mer(y,z));}
void pr(int x){if(!x)return; down(x),pr(ls[x]),cout<<val[x]<<" ",pr(rs[x]);} int main(){
int n,m; cin>>n>>m;
for(int i=1;i<=n;i++)R=mer(R,nd(i));
for(int i=1,l,r;i<=m;i++)cin>>l>>r,modify(l,r);
pr(R);
return 0;
}

III. P2042 [NOI2005] 维护数列

平衡树维护序列的练手题,不过 FHQ Treap 的常数有点大,下面这份代码只有开了 O2 才能过。

#include <bits/stdc++.h>
using namespace std; #define gc getchar() inline int read(){
int x=0,sgn=0; char s=gc;
while(!isdigit(s))sgn|=s=='-',s=gc;
while(isdigit(s))x=x*10+s-'0',s=gc;
return sgn?-x:x;
} const int N=5e5+5;
const int inf=1e9+7; int R,node,sz[N],ls[N],rs[N],val[N],rd[N];
int top,stc[N],tg[N],mod[N]; struct data{
int sum,pre,suc,mx;
void modify(int x,int sz){sum=x*sz,pre=suc=mx=x<0?x:sum;}
friend data operator + (data a,data b){
data c;
c.sum=a.sum+b.sum;
c.pre=max(a.pre,a.sum+b.pre);
c.suc=max(b.suc,b.sum+a.suc);
c.mx=max(max(a.mx,b.mx),a.suc+b.pre);
return c;
}
}d[N],emp; void push(int x){
sz[x]=sz[ls[x]]+sz[rs[x]]+1;
d[x]=d[ls[x]]+(data){val[x],val[x],val[x],val[x]}+d[rs[x]];
}
void down(int x){
if(tg[x]){
tg[ls[x]]^=1,tg[rs[x]]^=1;
swap(d[ls[x]].pre,d[ls[x]].suc);
swap(d[rs[x]].pre,d[rs[x]].suc);
swap(ls[x],rs[x]),tg[x]=0;
}
if(mod[x]!=inf){
val[ls[x]]=val[rs[x]]=mod[ls[x]]=mod[rs[x]]=mod[x];
d[ls[x]].modify(mod[x],sz[ls[x]]);
d[rs[x]].modify(mod[x],sz[rs[x]]);
mod[x]=inf;
}
}
int nd(int v){
int x=top?stc[top--]:++node;
sz[x]=1,val[x]=v,rd[x]=rand();
d[x]={v,v,v,v},mod[x]=inf;
return x;
}
void dd(int x){
sz[x]=ls[x]=rs[x]=val[x]=rd[x]=tg[x]=0;
d[x]=emp,stc[++top]=x;
} void spl(int p,int v,int &x,int &y){
if(!p)return x=y=0,void();
down(p);
if(sz[ls[p]]>=v)spl(ls[p],v,x,ls[y=p]);
else spl(rs[p],v-sz[ls[p]]-1,rs[x=p],y);
push(p);
}
int mer(int x,int y){
if(!x||!y)return x|y;
down(x),down(y);
if(rd[x]>rd[y])return rs[x]=mer(rs[x],y),push(x),x;
return ls[y]=mer(x,ls[y]),push(y),y;
}
void empty(int x){
if(!x)return;
empty(ls[x]),empty(rs[x]),dd(x);
} void ins(){
int p=read(),t=read(),x=0,y=0,rt=0;
for(int i=1;i<=t;i++)rt=mer(rt,nd(read()));
spl(R,p,x,y),R=mer(mer(x,rt),y);
}
void spl(int &x,int &y,int &z){
int p=read(),t=read(); p--;
spl(R,p+t,x,z),spl(x,p,x,y);
}
void del(){int x,y,z; spl(x,y,z),empty(y),R=mer(x,z);}
void mak(){int x,y,z; spl(x,y,z),mod[y]=val[y]=read(),d[y].modify(val[y],sz[y]); R=mer(mer(x,y),z);}
void rev(){int x,y,z; spl(x,y,z),tg[y]^=1,R=mer(mer(x,y),z);}
void get(){int x,y,z; spl(x,y,z),printf("%d\n",d[y].sum),R=mer(mer(x,y),z);} int main(){
d[0]=emp={0,-inf,-inf,-inf};
int n,m; cin>>n>>m;
for(int i=1;i<=n;i++)R=mer(R,nd(read()));
for(int i=1;i<=m;i++){
string s; cin>>s;
if(s[0]=='I')ins();
else if(s[0]=='D')del();
else if(s[0]=='R')rev();
else if(s[0]=='G')get();
else if(s[2]=='K')mak();
else printf("%d\n",d[R].mx);
}
return 0;
}

IV. P4008 [NOI2003] 文本编辑器

对顶堆开 O2 可以过!!O2 永远滴神!!

#include <bits/stdc++.h>
using namespace std; #define gc getchar() inline int read(){
int x=0,sgn=0; char s=gc;
while(!isdigit(s))sgn|=s=='-',s=gc;
while(isdigit(s))x=x*10+s-'0',s=gc;
return sgn?-x:x;
} const int N=3e6+5; int t,at,bt;
char a[N],b[N],s[N];
string op; int main(){
cin>>t;
for(int i=1,x;i<=t;i++){
cin>>op;
if(op.size()<3)cin>>op;
if(op[0]=='M'){
x=read();
while(at>x)b[++bt]=a[at--];
while(at<x)a[++at]=b[bt--];
}
if(op[0]=='I'){
int len=0; cin>>x;
while(len<x){
s[++len]=gc;
if(s[len]<32||s[len]>126)len--;
}
for(int j=x;j;j--)b[++bt]=s[j];
}
if(op[0]=='D'){cin>>x; while(x--)bt--;}
if(op[0]=='G'){
int pos=bt; cin>>x;
while(x--)putchar(b[pos--]);
putchar('\n');
}
if(op[0]=='P')b[++bt]=a[at--];
if(op[0]=='N')a[++at]=b[bt--];
}
return 0;
}

接下来是 FHQ 版本:

#include <bits/stdc++.h>
using namespace std; #define gc getchar() inline int read(){
int x=0,sgn=0; char s=gc;
while(!isdigit(s))sgn|=s=='-',s=gc;
while(isdigit(s))x=x*10+s-'0',s=gc;
return sgn?-x:x;
} const int N=2e6+5; int R,K,node,ls[N],rs[N],sz[N],rd[N];
char val[N]; void push(int x){sz[x]=sz[ls[x]]+sz[rs[x]]+1;}
int nd(char v){
int x=++node;
rd[x]=rand(),sz[x]=1,val[x]=v;
return x;
} void spl(int p,int l,int &x,int &y){
if(!p)return x=y=0,void();
if(sz[ls[p]]>=l)spl(ls[p],l,x,ls[y=p]);
else spl(rs[p],l-sz[ls[p]]-1,rs[x=p],y);
push(p);
}
int mer(int x,int y){
if(!x||!y)return x|y;
if(rd[x]>rd[y])return rs[x]=mer(rs[x],y),push(x),x;
return ls[y]=mer(x,ls[y]),push(y),y;
}
void print(int x){
if(!x)return;
print(ls[x]),putchar(val[x]),print(rs[x]);
} int t;
string op;
int main(){
cin>>t;
for(int i=1,n;i<=t;i++){
cin>>op;
if(op[0]=='M')K=read();
else if(op[0]=='I'){
n=read();
int x,y; spl(R,K,x,y);
for(int i=1;i<=n;i++){
char s=gc;
while(s<32||s>126)s=gc;
x=mer(x,nd(s));
} R=mer(x,y);
} else if(op[0]=='D'){
n=read();
int x,y,z;
spl(R,K+n,x,z),spl(x,K,x,y),R=mer(x,z);
} else if(op[0]=='G'){
n=read();
int x,y,z;
spl(R,K+n,x,z),spl(x,K,x,y),print(y);
R=mer(mer(x,y),z),putchar('\n');
} else if(op[0]=='P')K--;
else K++;
}
return 0;
}

V. P4146 序列终结者

可以说是板子题了,注意初始化编号为 \(0\) 的节点。

#include <bits/stdc++.h>
using namespace std; #define ll long long const int N=1e5+5; ll R,node,val[N],ls[N],rs[N],sz[N],rd[N],tg[N],add[N],mx[N];
void push(int x){sz[x]=sz[ls[x]]+sz[rs[x]]+1,mx[x]=max(mx[ls[x]],max(mx[rs[x]],val[x]));}
void down(int x){
if(tg[x])tg[ls[x]]^=1,tg[rs[x]]^=1,swap(ls[x],rs[x]),tg[x]=0;
if(add[x]){
add[ls[x]]+=add[x],add[rs[x]]+=add[x];
mx[ls[x]]+=add[x],mx[rs[x]]+=add[x];
val[ls[x]]+=add[x],val[rs[x]]+=add[x];
add[x]=0;
}
}
int nd(){int x=++node; sz[x]=1,rd[x]=rand(); return x;} void spl(int p,int k,ll &x,ll &y){
if(!p)return x=y=0,void();
down(p);
if(sz[ls[p]]>=k)spl(ls[p],k,x,ls[y=p]);
else spl(rs[p],k-sz[ls[p]]-1,rs[x=p],y);
push(p);
}
int mer(int x,int y){
if(!x||!y)return x|y;
down(x),down(y);
if(rd[x]>rd[y])return rs[x]=mer(rs[x],y),push(x),x;
return ls[y]=mer(x,ls[y]),push(y),y;
} int n,m;
int main(){
cin>>n>>m,mx[0]=-1e18;
for(int i=1;i<=n;i++)R=mer(R,nd());
for(int i=1;i<=m;i++){
int k,l,r,v; cin>>k>>l>>r;
if(k==1){
cin>>v;
ll x,y,z; spl(R,r,x,z),spl(x,l-1,x,y);
add[y]+=v,mx[y]+=v,val[y]+=v;
R=mer(mer(x,y),z);
}
if(k==2){
ll x,y,z; spl(R,r,x,z),spl(x,l-1,x,y);
tg[y]^=1,R=mer(mer(x,y),z);
}
if(k==3){
ll x,y,z; spl(R,r,x,z),spl(x,l-1,x,y);
printf("%lld\n",mx[y]),R=mer(mer(x,y),z);
}
}
return 0;
}

VI. P3960 [NOIP2017 提高组] 列队

来点不那么套路的平衡树题。

众所周知平衡树可以用来维护序列,那么最后一列删除和插入一个数就用 FHQ Treap 维护。对于每一行,在其末尾插入的数也用 FHQ Treap 维护。如果这一行删除的数不足以让当前位置被曾经到过最后一列的数所占据,那么可以动态开点线段树标记原来哪些数被取到了(对于前 \(m-1\) 列),那么现在就要取该行从左往右数第 \(y\) 个没有被取的数,这个可以线段树上二分。因此总时间复杂度为 \(\mathcal{O}((n+q)\log n)\)。

#include <bits/stdc++.h>
using namespace std; #define int long long const int N=3e5+5; struct FHQ{
int node,R[N],ls[N<<1],rs[N<<1],rd[N<<1],sz[N<<1],val[N<<1];
void push(int x){sz[x]=sz[ls[x]]+sz[rs[x]]+1;}
int nd(int v){int x=++node; return val[x]=v,rd[x]=rand(),sz[x]=1,x;}
void spl(int p,int k,int &x,int &y){
if(!p)return x=y=0,void();
if(sz[ls[p]]>=k)spl(ls[p],k,x,ls[y=p]);
else spl(rs[p],k-sz[ls[p]]-1,rs[x=p],y);
push(p);
}
int mer(int x,int y){
if(!x||!y)return x|y;
if(rd[x]>rd[y])return rs[x]=mer(rs[x],y),push(x),x;
return ls[y]=mer(x,ls[y]),push(y),y;
}
}tr; struct SEG{
int node,R[N],val[N<<5],ls[N<<5],rs[N<<5];
void ins(int l,int r,int p,int &x){
if(!x)x=++node; val[x]++;
if(l==r)return;
int m=l+r>>1;
if(p<=m)ins(l,m,p,ls[x]);
else ins(m+1,r,p,rs[x]);
}
int query(int l,int r,int k,int x){
if(l==r)return l;
int m=l+r>>1,v=(m-l+1)-val[ls[x]];
if(k<=v)return query(l,m,k,ls[x]);
return query(m+1,r,k-v,rs[x]);
}
}sg; int n,m,q;
signed main(){
cin>>n>>m>>q;
for(int i=1,j=m;i<=n;i++,j+=m)tr.R[0]=tr.mer(tr.R[0],tr.nd(j));
for(int i=1;i<=q;i++){
int x,y,res,a,b,c; cin>>x>>y;
if(y<m){
int sz=tr.sz[tr.R[x]];
if(y<m-sz){
int cnt=sg.query(1,m-1,y,sg.R[x]);
cout<<(res=(x-1)*m+cnt)<<endl;
sg.ins(1,m-1,cnt,sg.R[x]);
}
else{
tr.spl(tr.R[x],y-(m-sz),a,b),tr.spl(b,1,b,c);
cout<<(res=tr.val[b])<<endl,tr.R[x]=tr.mer(a,c);
}
}
tr.spl(tr.R[0],x-1,a,b),tr.spl(b,1,b,c);
if(y<m)tr.R[x]=tr.mer(tr.R[x],b);
else cout<<(res=tr.val[b])<<endl;
tr.R[0]=tr.mer(a,tr.mer(c,tr.nd(res)));
}
return 0;
}

感觉可以只维护若干个队列 + 线段树做,然后线段树二分转为 BIT 上二分,常数就可以大大减小了。

*VII. P7739 [NOI2021] 密码箱

好了,现在你已经学会了 FHQ Treap 的基本操作,快来试试这道题吧!

别看这道题被喷得很惨,其中的内涵还是值得钻研的。

首先,重要的一点是分数不会被约分:\(a_i+\dfrac{1}{\frac{x}{y}}=\dfrac{a_ix+y}{x}\),显然 \(\gcd(xa_i+y,x)=\gcd(x,y)\)。因此,我们可以:

  • Trick 1:用矩阵维护线性变换

    考察一次变换过后,分数究竟如何变化。分子:\(x\to a_ix+y\);分母:\(y\to x\)。不难发现这是一个二维向量的线性变换:\(\begin{bmatrix}x&y\end{bmatrix}\to\begin{bmatrix}a_ix+y&x\end{bmatrix}\)。不难发现我们只需要左乘上矩阵 \(\begin{bmatrix}a_i&1\\1&0\end{bmatrix}\) 即可。

    • 对于一个 W 操作,考虑 \(\begin{bmatrix}1&k\\0&1\end{bmatrix}\times \begin{bmatrix}a_i&1\\1&0\end{bmatrix}=\begin{bmatrix}a_i+k&1\\1&0\end{bmatrix}\),所以相当于在序列最右边加入添加矩阵 \(\begin{bmatrix}1&1\\0&1\end{bmatrix}\)。
    • 对于一个 E 操作,因为当最后一项为 \(1\) 时,给倒数第二项加 \(1\) 与先给数列的最后一项减 \(1\),接着在数列末端加两个 \(1\) 是等价的,所以可以直接考虑后者:\(\begin{bmatrix}1&1\\0&1\end{bmatrix}\times \begin{bmatrix}1&1\\0&1\end{bmatrix}\times \begin{bmatrix}1&-1\\0&1\end{bmatrix}=\begin{bmatrix}2&-1\\1&0\end{bmatrix}\)​。

    接下来就可以用平衡树维护一整个序列了。不过对于操作 FLIPREVERSE 似乎不太好办。

  • Trick 2:对于没有交换律的元素的区间翻转,预处理出正序值和逆序值。

    一个非常巧妙且实用的技巧。后两种操作都可以这么办,这样就做完了。视 \(n,q\) 同阶,则时间复杂度为 \(\mathcal{O}(n\log n)\)。

#include <bits/stdc++.h>
using namespace std; typedef double db;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull; #define gc getchar()
#define pb push_back
#define mem(x,v,n) memset(x,v,sizeof(int)*n)
#define cpy(x,y,n) memcpy(x,y,sizeof(int)*n) const ld Pi=acos(-1);
const ll mod=998244353; inline int read(){
int x=0; char s=gc;
while(!isdigit(s))s=gc;
while(isdigit(s))x=x*10+s-'0',s=gc;
return x;
} const int N=2e5+5; struct Matrix{
ll a,b,c,d;
Matrix operator * (Matrix x){
Matrix y;
y.a=(a*x.a+b*x.c)%mod;
y.b=(a*x.b+b*x.d)%mod;
y.c=(c*x.a+d*x.c)%mod;
y.d=(c*x.b+d*x.d)%mod;
return y;
}
}E,W,B,tr[N][2],val[N][2],rev[N][2]; int type,R,node,rd[N],sz[N],ls[N],rs[N],rv[N],flp[N];
void push(int x){
// cout<<"push "<<x<<endl;
int l=ls[x],r=rs[x];
sz[x]=sz[l]+sz[r]+1;
val[x][0]=val[r][0]*tr[x][0]*val[l][0];
val[x][1]=val[r][1]*tr[x][1]*val[l][1];
rev[x][0]=rev[l][0]*tr[x][0]*rev[r][0];
rev[x][1]=rev[l][1]*tr[x][1]*rev[r][1];
}
int nd(char v){
int x=++node;
rd[x]=rand(),sz[x]=1;
if(v=='E')tr[x][0]=E,tr[x][1]=W;
else tr[x][0]=W,tr[x][1]=E;
val[x][0]=rev[x][0]=tr[x][0];
val[x][1]=rev[x][1]=tr[x][1];
return x;
} void reverse(int x){
swap(rev[x],val[x]);
swap(ls[x],rs[x]);
}
void flip(int x){
swap(val[x][0],val[x][1]);
swap(rev[x][0],rev[x][1]);
swap(tr[x][0],tr[x][1]);
} void down(int x){
if(rv[x]){
reverse(ls[x]),reverse(rs[x]);
rv[ls[x]]^=1,rv[rs[x]]^=1,rv[x]=0;
}
if(flp[x]){
flip(ls[x]),flip(rs[x]);
flp[ls[x]]^=1,flp[rs[x]]^=1,flp[x]=0;
}
} int mer(int x,int y){
if(!x||!y)return x|y;
down(x),down(y);
if(rd[x]>rd[y])return rs[x]=mer(rs[x],y),push(x),x;
return ls[y]=mer(x,ls[y]),push(y),y;
}
void spl(int p,int v,int &x,int &y){
if(!p)return x=y=0,void();
down(p);
if(v<=sz[ls[p]])spl(ls[p],v,x,ls[y=p]);
else spl(rs[p],v-sz[ls[p]]-1,rs[x=p],y);
push(p);
} void flip(int l,int r){
int x,y,z;
spl(R,r,x,z),spl(x,l-1,x,y);
flip(y),flp[y]^=1;
R=mer(mer(x,y),z);
}
void reverse(int l,int r){
int x,y,z;
spl(R,r,x,z),spl(x,l-1,x,y);
reverse(y),rv[y]^=1;
R=mer(mer(x,y),z);
}
void print(){
Matrix ans=val[R][0]*W;
cout<<ans.a<<" "<<ans.b<<endl;
} int n,q;
char s[N],v;
int main(){
scanf("%d%d%s",&n,&q,s+1),srand(time(0));
W={1,1,0,1},E={2,mod-1,1,0};
val[0][0]=val[0][1]=rev[0][0]=rev[0][1]={1,0,0,1};
for(int i=1;i<=n;i++)R=mer(R,nd(s[i])); print();
for(int i=1,l;i<=q;i++){
scanf("%s",s+1);
if(s[1]=='A')cin>>v,R=mer(R,nd(v));
if(s[1]=='F')l=read(),flip(l,read());
if(s[1]=='R')l=read(),reverse(l,read());
print();
}
return 0;
}

平衡树 & LCT的更多相关文章

  1. 目标&计划

    目标 感觉起来NOIP还是能考到一个比较好的分数的吧 550+? 现在可能还不大行,但是过3个月或许还是能考到的 所以先订下NOIP保底500争取550+吧 至于省选... 前面有一群巨佬挡着,感觉想 ...

  2. ORCHARD WOODEN GATE

    狗: 代码小盒子 爆零秘籍 备忘录 任务计划 核心算法: 搜索/枚举/贪心 dp 分治 数据结构: 并查集 ST表 堆 线段树 树状数组 分块 树套树 平衡树 LCT 莫队 字符串: 哈希 Trie ...

  3. 总结-一本通提高篇&算竞进阶记录

    当一个人看见星空,就再无法忍受黑暗 为了点亮渐渐沉寂的星空 不想就这样退役 一定不会鸽の坑 . 一本通提高篇 . 算竞进阶 . CDQ & 整体二分 . 平衡树 . LCT . 字符串 . 随 ...

  4. [HNOI2010]弹飞绵羊 (平衡树,LCT动态树)

    题面 题解 因为每个点都只能向后跳到一个唯一的点,但可能不止一个点能跳到后面的某个相同的点, 所以我们把它抽象成一个森林.(思考:为什么是森林而不是树?) 子节点可以跳到父节点,根节点再跳就跳飞了. ...

  5. 可持久化Trie & 可持久化平衡树 专题练习

    [xsy1629]可持久化序列 - 可持久化平衡树 http://www.cnblogs.com/Sdchr/p/6258827.html [bzoj4260]REBXOR - Trie 事实上只是一 ...

  6. BZOJ_3282_Tree_(LCT)

    描述 http://www.lydsy.com/JudgeOnline/problem.php?id=3282 给出n个点以及权值,四种操作: 0.求x,y路径上的点权值的异或和. 1.连接x,y. ...

  7. 动态树LCT小结

    最开始看动态树不知道找了多少资料,总感觉不能完全理解.但其实理解了就是那么一回事...动态树在某种意思上来说跟树链剖分很相似,都是为了解决序列问题,树链剖分由于树的形态是不变的,所以可以通过预处理节点 ...

  8. 学习笔记::LCT

    今天听见茹大神20分钟讲完了LCT,10分钟讲完平衡树,5分钟讲完树剖,感觉自己智商还不及他一半... 还有很多不懂:2017/1/15 的理解: access是干什么用的? 不知道,只知道他是用来把 ...

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

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

随机推荐

  1. 保护模式篇——TLB与CPU缓存

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  2. 基于Vue的工作流项目模块中,使用动态组件的方式统一呈现不同表单数据的处理方式

    在基于Vue的工作流项目模块中,我们在查看表单明细的时候,需要包含公用表单信息,特定表单信息两部分内容.前者表单数据可以统一呈现,而后者则是不同业务的表单数据不同.为了实现更好的维护性,把它们分开作为 ...

  3. 常用Java API:HashMap 和 TreeMap

    摘要 本文主要介绍Map接口下的HashMap和TreeMap. HashMap HashMap是基于哈希表的 Map 接口的实现,是无序的 clear()//清空. containsKey(Obje ...

  4. 21.10.9 test

    T1 购票方案 \(\color{green}{100}\) 对于每个时间节点维护它作为每种票所能包含的最后一个点时,这种票的起始点位置,由于这个位置是单调的,所以类似双指针维护,\(O(KN)\) ...

  5. c++继承关系中成员函数的重载、重写、重定义之间的区别

    1.Override.Overload.Redefine Overload 重载只能发生在类内部,不能发生在子类和父类的继承中.具体来说,如果子类中有父类同名.同返回值类型,但是不同参数列表,这两个在 ...

  6. hdu 5092 Seam Carving (简单数塔DP,题没读懂,,不过可以分析样例)

    题意: 给一个m*n的矩阵,每格上有一个数. 找从第1行到第m行的一条路径,使得这条路径上的数之和最小. 路径必须满足相邻两行所选的两个数的纵坐标相邻(即一个格子必须是另一个格子的周围八个格子中的一个 ...

  7. axios & fetch 异步请求

    // 一.创建实例 const request = axios.create({ baseURL: "http://kg.zhaodashen.cn/v2", headers: { ...

  8. Vue首屏性能优化组件

    Vue首屏性能优化组件 简单实现一个Vue首屏性能优化组件,现代化浏览器提供了很多新接口,在不考虑IE兼容性的情况下,这些接口可以很大程度上减少编写代码的工作量以及做一些性能优化方面的事情,当然为了考 ...

  9. Apache Hudi在华米科技的应用-湖仓一体化改造

    徐昱 Apache Hudi Contributor:华米高级大数据开发工程师 巨东东 华米大数据开发工程师 1. 应用背景及痛点介绍 华米科技是一家基于云的健康服务提供商,拥有全球领先的智能可穿戴技 ...

  10. 大爽Python入门教程 3-3 循环:`for`、`while`

    大爽Python入门公开课教案 点击查看教程总目录 for循环 可迭代对象iterable 不同于其他语言. python的for循环只能用于遍历 可迭代对象iterable 的项. 即只支持以下语法 ...