平衡树 & LCT

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;
}
posted @ 2021-08-01 15:21  qAlex_Weiq  阅读(3033)  评论(11编辑  收藏  举报