Day 1
轻重边
description
给出一棵树,初始时每条边都是轻边。有(m) 次操作。操作分如下两种:
- 给定两点(a,b) ,先对于(a,b) 路径上所有点(x) ,将与(x) 相连的边全部变为轻边,然后再将处于(a,b) 路径上的边变为重边。(修改)
- 给定两点(a,b) ,询问其路径上有多少条重边。(询问)
(n,mle 10^5)
solution
我们可以从点的方面进行考虑。考虑每次修改时将对应路径上所有点都标记上一个新的时间戳,那么一条边是重边当且仅当其两端点的时间戳相同。(因此初始时需要特殊构造)那么询问可以转化为有多少对相邻点颜色相同,这个可以使用线段树进行维护。于是树链剖分+线段树即可,稍微注意下边界情况。
复杂度(mathcal O(nlog^2n)) 。
code
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
int s=0,w=1; char ch=getchar();
for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
return s*w;
}
const int N=1e5+5,inf=0x3f3f3f3f;
int n,m;vector<int>e[N];
inline void add(int x,int y){e[x].push_back(y);}
namespace SGT
{
int ct[N<<2],lc[N<<2],rc[N<<2],tag[N<<2];
#define ls (rt<<1)
#define rs (rt<<1|1)
inline void up(int rt)
{
ct[rt]=ct[ls]+ct[rs]-(rc[ls]==lc[rs]);
lc[rt]=lc[ls],rc[rt]=rc[rs];
}
inline void cov(int rt,int c){lc[rt]=rc[rt]=c;ct[rt]=1;tag[rt]=c;}
inline void dn(int rt)
{
if(!tag[rt])return;int&tg=tag[rt];
cov(ls,tg),cov(rs,tg),tg=0;
}
void update(int rt,int l,int r,int ql,int qr,int c)
{
if(ql<=l&&r<=qr)return cov(rt,c);
dn(rt);int mid=(l+r)>>1;
if(ql<=mid)update(ls,l,mid,ql,qr,c);
if(mid<qr)update(rs,mid+1,r,ql,qr,c);
up(rt);
}
int query(int rt,int l,int r,int ql,int qr,int&lcc,int&rcc)
{
if(ql<=l&&r<=qr)
{
if(l==ql)lcc=lc[rt];
if(r==qr)rcc=rc[rt];
return ct[rt];
}
dn(rt);int mid=(l+r)>>1;
if(qr<=mid)return query(ls,l,mid,ql,qr,lcc,rcc);
if(mid<ql)return query(rs,mid+1,r,ql,qr,lcc,rcc);
int tl=0,tr=0;
int r1=query(ls,l,mid,ql,mid,lcc,tl);
int r2=query(rs,mid+1,r,mid+1,qr,tr,rcc);
return r1+r2-(tl==tr);
}
void clear(int rt,int l,int r)
{
ct[rt]=lc[rt]=rc[rt]=tag[rt]=0;
if(l==r)return;int mid=(l+r)>>1;
clear(ls,l,mid),clear(rs,mid+1,r);
}
#undef ls
#undef rs
}
int sz[N],dep[N],son[N],top[N],id[N],tim,fa[N];
void dfs1(int u,int f)
{
sz[u]=1;dep[u]=dep[f]+1;
int mx=0;fa[u]=f;son[u]=0;
for(int v:e[u])if(v^f)
{
dfs1(v,u);sz[u]+=sz[v];
if(sz[v]>mx)mx=sz[v],son[u]=v;
}
}
void dfs2(int u,int tp)
{
id[u]=++tim;top[u]=tp;
SGT::update(1,1,n,id[u],id[u],(dep[u]&1)?0:-1);
if(son[u])dfs2(son[u],tp);
for(int v:e[u])if(v!=fa[u]&&v!=son[u])dfs2(v,v);
}
inline int lca(int x,int y)
{
while(top[x]^top[y])
{
if(dep[top[x]]<dep[top[y]])swap(x,y);
x=fa[top[x]];
}
return dep[x]<dep[y]?x:y;
}
inline int dis(int x,int y){return dep[x]+dep[y]-2*dep[lca(x,y)];}
inline void upline(int x,int y,int t)
{
while(top[x]^top[y])
{
if(dep[top[x]]<dep[top[y]])swap(x,y);
SGT::update(1,1,n,id[top[x]],id[x],t);
x=fa[top[x]];
}
if(id[x]>id[y])swap(x,y);
SGT::update(1,1,n,id[x],id[y],t);
}
inline int q(int x,int y)
{
int cx=inf,dx=inf,cy=inf,dy=inf,tmp=inf,ans=0,d=dis(x,y);
while(top[x]^top[y])
{
if(dep[top[x]]<dep[top[y]])
{
ans+=SGT::query(1,1,n,id[top[y]],id[y],tmp,dy);
if(dy==cy)--ans;cy=tmp;
y=fa[top[y]];
}
else
{
ans+=SGT::query(1,1,n,id[top[x]],id[x],tmp,dx);
if(dx==cx)--ans;cx=tmp;
x=fa[top[x]];
}
}
if(id[x]>id[y])swap(x,y),swap(cx,cy);
ans+=SGT::query(1,1,n,id[x],id[y],dx,dy);
if(dx==cx)--ans;if(dy==cy)--ans;
return d-ans+1;
}
int main()
{
int T=read();
while(T-->0)
{
n=read(),m=read();
for(int i=1,u,v;i<n;++i)
u=read(),v=read(),add(u,v),add(v,u);
dfs1(1,0);dfs2(1,1);
for(int i=1;i<=m;++i)
{
int op=read(),a=read(),b=read();
if(op==1)upline(a,b,i);
else printf("%d
",q(a,b));
}
for(int i=1;i<=n;++i)e[i].clear();//SGT::clear(1,1,n);
tim=0;
}
return 0;
}
路径交点
description
以这样的方式给出一个图。首先给定层数(k) 以及(n_1,n_2,cdots,n_k) ,满足(n_1=n_k,forall iin[2,k-1],n_iin[n_1,2n_1]) ,表示第(i) 层恰好有(n_i) 个点,编号为(1sim n_i)。而后给出边,所有边都满足起点在第(i) 层而终点在第(i+1) 层,其中(iin[1,k-1]) 。定义一簇路径为(n_1) 条从第一层出发最终到达第(k) 层的路径且满足不存在一个点使得其被超过一条路径经过。
对于两条路径(P,Q) ,假设它们在第(i) 层和第(i+1) 层之间的边为((P_i,P_{i+1}),(Q_i,Q_{i+1})) ,则我们称二者在第(j) 层后有一个交点当且仅当(P_i-Q_i,P_{i+1}-Q_{i+1}) 异号。定义两条路径交点数为所有层后的交点总数。定义一簇路径的交点数为两两路径交点数之和。
求偶数个交点数的方案减去及数个交点数的方案对(998244353) 取模后的值。
(n1le 100,kle 100)
solution
不相交路径让我们联想到(LGV) 引理,它是这样描述的,令
其中(A_1,A_2,cdots,A_n) 和(B_1,B_2,cdots,B_n) 分别表示起点和终点,(e(A_i,B_j)) 表示从第(i) 个起点到第(j) 个终点的方案数,那么有
其中(p) 表示(1sim n) 的排列,表示一组不相交的路径(S) ,其中(S_i) 为一条从(A_i) 到(B_{p_i}) 的路径。而(N(p)) 代表排列(p) 的逆序对个数。注意这里只是呈现了该引理的简化形式。
容易发现题目中所谓的交点其实就是逆序对。因此当(k=2) 时可以直接套用(LGV) 引理。其他情况下,可以考虑两条路径,容易发现增加层数并不会影响其交点个数的奇偶性(具体证明应该类似勘根定理),因此仍然可以套用(LGV) 引理。求路径数量可以采用矩阵乘法。
复杂度(mathcal O(n^4)) 。
code
#include<bits/stdc++.h>
using namespace std;
const int N=105,mod=998244353;
inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
inline void inc(int&x,int y){x=add(x,y);}
inline int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
inline void rec(int&x,int y){x=dec(x,y);}
inline int qpow(int x,int y,int res=1)
{
for(;y;y>>=1,x=1ll*x*x%mod)
(y&1)&&(res=1ll*res*x%mod);
return res;
}
struct Matrix
{
int a,b,c[N<<1][N<<1];
inline void pre(int _a,int _b)
{a=_a,b=_b;for(int i=1;i<=a;++i)for(int j=1;j<=b;++j)c[i][j]=0;}
inline int det()
{
assert(a==b);
int ret=1;
for(int i=1;i<=a;++i)
{
if(!c[i][i])
for(int j=i+1;j<=a;++j)
if(c[j][i]){swap(c[i],c[j]);ret=mod-ret;break;}
if(!c[i][i])return 0;
ret=1ll*ret*c[i][i]%mod;
int iv=qpow(c[i][i],mod-2);
for(int j=i;j<=a;++j)c[i][j]=1ll*c[i][j]*iv%mod;
for(int j=i+1;j<=a;++j)
if(c[j][i])
for(int k=a;k>=i;--k)
rec(c[j][k],1ll*c[i][k]*c[j][i]%mod);
}
return ret;
}
}A[N];
inline Matrix operator*(const Matrix&x,const Matrix&y)
{
assert(x.b==y.a);
Matrix z;z.pre(x.a,y.b);
for(int i=1;i<=z.a;++i)
for(int j=1;j<=z.b;++j)
for(int k=1;k<=x.b;++k)
inc(z.c[i][j],1ll*x.c[i][k]*y.c[k][j]%mod);
return z;
}
int n[N],m[N];
int main()
{
int T;scanf("%d",&T);
while(T-->0)
{
int k;scanf("%d",&k);
for(int i=1;i<=k;++i)scanf("%d",n+i);
for(int i=1;i<k;++i)A[i].pre(n[i],n[i+1]);
for(int i=1;i<k;++i)scanf("%d",m+i);
for(int i=1;i<k;++i)
while(m[i]--)
{
int x,y;scanf("%d%d",&x,&y);
A[i].c[x][y]=1;
}
Matrix T=A[1];
for(int i=2;i<k;++i)T=T*A[i];
printf("%d
",T.det());
}
return 0;
}
庆典
descirption
给定(n) 个点(m) 条边的有向图(G) ,满足对于任意三个点(x,y,z) ,若从(x) 和(y)出发均能到达(z) ,那么(x) 能到达(y) 或(y) 能到达(z) 。现在有(q) 次询问,每次询问给出(s_i,t_i) ,要求从(s_i) 出发经过一些点到达(t_i) 。一个点可被经过多次。同时,每次询问时可以临时给定(k) 条有向边(不一定满足原先图的性质)。现在对于每次询问求出其可能会经过多少个点。
(n,qle 3 imes 10^5,kle 2)
solution
容易想到先将图(G) 进行缩点,因为对于一个强连通分量里的点来说它们的可被经过性是一致的。
图(G) 有奇怪的性质,我们考虑这个性质应该如何使用。若缩点后(x) 能通过一条边就到达(y) ,记(x ightarrow y) 。缩点后原图变为一个(DAG) ,对于点(z) ,若(exist x,y ext{s.t.}x ightarrow z,y ightarrow z) ,那么必然有(x ightarrow y) 或(y ightarrow x) 。不妨设为(x ightarrow y) ,那么可以发现若删去边(x ightarrow z) 是不会对答案产生影响的。通过这种方法,我们可以将这个(DAG) 转化为一棵外向树。
由于(kle 2) ,因此我们可以通过分类讨论来回答询问,但这实在是过于繁琐。我们可以换一种方式来刻画:即从(s) 出发可以到达的点和可以到达(t) 的点的交即是所求答案。而从(s) 出发可以到达的点就是一些子树的并,可以到达(t) 的点则是到根的路径并。可以通过暴力容斥或者虚树来求得答案。
code
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int n,m,q,k;
namespace G1
{
vector<int>e[N];
int dfn[N],low[N],tim,id[N],sta[N],top,cnt,num[N];bool insta[N];
void dfs(int u)
{
low[u]=dfn[u]=++tim;
sta[++top]=u,insta[u]=1;
for(int v:e[u])
{
if(!dfn[v])
dfs(v),low[u]=min(low[u],low[v]);
else if(insta[v])low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
++cnt;int v;
do
{
v=sta[top--];insta[v]=0;
++num[cnt],id[v]=cnt;
}while(v!=u);
}
}
inline void main()
{
for(int i=1,u,v;i<=m;++i)
scan(u),scan(v),e[u].push_back(v);
for(int i=1;i<=n;++i)if(!dfn[i])dfs(i);
}
}
using G1::id;using G1::cnt;using G1::num;
namespace G2
{
vector<int>G[N];int fa[N],in[N],out[N],tim,s[N],rt,a[5],b[5],pa[N][20],dep[N];
void dfs(int u)
{
in[u]=++tim;dep[u]=dep[fa[u]]+1;
s[u]=num[u]+s[fa[u]];
pa[u][0]=fa[u];
for(int i=1;;++i)
{
pa[u][i]=pa[pa[u][i-1]][i-1];
if(!pa[u][i])break;
}
for(int v:G[u])dfs(v);
out[u]=++tim;
}
inline bool isac(int x,int y){return in[x]<=in[y]&&out[y]<=out[x];}
inline int lca(int x,int y)
{
if(dep[x]>dep[y])swap(x,y);
if(isac(x,y))return x;
for(int i=18;~i;--i)
if(pa[x][i]&&!isac(pa[x][i],y))
x=pa[x][i];
return fa[x];
}
set<int>tx,ty;int tmp[5];
inline void ins1(int u)
{
int ut=0;
for(auto v:tx)
if(isac(u,v))tmp[++ut]=v;
else if(isac(v,u))return;
for(int i=1;i<=ut;++i)tx.erase(tmp[i]);
tx.insert(u);
}
inline void ins2(int u)
{
int ut=0;
for(auto v:ty)
if(isac(u,v))return;
else if(isac(v,u))tmp[++ut]=v;
for(int i=1;i<=ut;++i)ty.erase(tmp[i]);
ty.insert(u);
}
inline int work(int u)
{
int ret=0;
for(auto v:tx)
ret+=isac(v,u)?s[u]-s[fa[v]]:0;
return ret;
}
inline int solve(int u,int v)
{
tx.clear(),ty.clear();
tx.insert(u),ty.insert(v);
int t=k;
while(t--)
for(int i=1;i<=k;++i)
{
bool flag=0;
for(auto v:tx)
if(isac(v,a[i])){flag=1;break;}
if(flag)ins1(b[i]);flag=0;
for(auto v:ty)
if(isac(b[i],v)){flag=1;break;}
if(flag)ins2(a[i]);
}
int ans=0,st=1<<ty.size(),ut=0;
for(auto v:ty)tmp[ut++]=v;
for(int i=1;i<st;++i)
{
int ret=0,tt=0;
for(int o=0;o<ut;++o)
if(i&(1<<o))ret=!ret?tmp[o]:lca(ret,tmp[o]),++tt;
ret=work(ret);
ans+=(tt&1)?-ret:ret;
}
return ans<0?-ans:ans;
}
inline void main()
{
for(int u=1;u<=n;++u)
for(int v:G1::e[u])
if(id[u]^id[v])
G[id[v]].push_back(id[u]);
for(int i=1;i<=cnt;++i)
{
if(G[i].empty()){rt=i;continue;}
fa[i]=*min_element(G[i].begin(),G[i].end());
}
for(int i=1;i<=cnt;++i)G[i].clear();
for(int i=1;i<=cnt;++i)if(rt^i)G[fa[i]].push_back(i);
dfs(rt);
for(int d=1;d<=q;++d)
{
int s,t;scan(s),scan(t);s=id[s],t=id[t];
for(int i=1;i<=k;++i)scan(a[i]),scan(b[i]),a[i]=id[a[i]],b[i]=id[b[i]];
putint(solve(s,t),'
');
}
}
}
int main()
{
scan(n),scan(m),scan(q),scan(k);
G1::main();G2::main();flush();
return 0;
}
Day 2
量子通信
给出(n) 个长度为(256) 的(01) 串,同时有(q) 组询问,每组询问给定长度为(256) 的(01) 串和(k) ,表示询问在给出的(n) 个串中是否存在一个串使得其与询问串只有至多(k) 位不同。强制在线了个寂寞。
(nle 4 imes 10^5,qle 1.2 imes 10^5,kle 16)
solution
注意到(kle 15) ,于是我们便想在(k) 上面做文章以优化暴力比较。
我们可以将长度为(256) 的(01) 串分割为(16) 个子串,每个子串长度为(16) 。根据抽屉原理,若某个询问串满足条件,那么必然有一个子串是相同的。
发现数据随机,于是我们可以使用vector记下第(i) 个子串为(j) 的原串的编号。这样询问时就可以枚举是第几个子串相同进而一一枚举进行比较。通过预处理可以(mathcal O(1)) 得到和某个子串相差多少位。这样下来平均运算次数为(mcdot 16 imes dfrac n{2^{16}}cdot 16approx 2 imes 10^8) 。稍微注意下常数即可通过。
code
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const int N = 400005;
bool s[N][256],t[256];int ss[N][16],tt[16];
inline ull myRand(ull &k1, ull &k2)
{
ull k3 = k1, k4 = k2;
k1 = k4;
k3 ^= (k3 << 23);
k2 = k3 ^ k4 ^ (k3 >> 17) ^ (k4 >> 26);
return k2 + k4;
}
inline void gen(int n, ull a1, ull a2)
{
for (int i = 1; i <= n; i++)
for (int j = 0; j < 256; j++)
s[i][j]=(myRand(a1, a2) & (1ull << 32)) ? 1 : 0;
}
char ch[1<<6];bool lans;
inline void read()
{
scanf("%s",ch);
for(int i=0;i<64;++i)
{
char c=ch[i];
if(c>='A')c-='A'-10;
else c^='0';
for(int j=((i+1)<<2)-1,o=0;o<4;--j,++o)
t[j]=(c>>o)&1;
}
if(lans)for(int i=0;i<256;++i)t[i]^=1;
}
inline void trans(bool a[],int b[])
{
for(int i=0;i<16;++i)
{
int&ret=b[i];ret=0;
for(int j=i<<4,c=0;c<16;++j,++c)
ret|=a[j]<<c;
}
}
int n,m,cnt[1<<16];ull a1,a2;
vector<int>ht[1<<4][1<<16];
int main()
{
// freopen("qi1.in","r",stdin);
scanf("%d%d%llu%llu",&n,&m,&a1,&a2);
gen(n,a1,a2);
for(int i=1;i<=n;++i)trans(s[i],ss[i]);
for(int i=1;i<=n;++i)
for(int j=0;j<16;++j)
ht[j][ss[i][j]].push_back(i);
cnt[0]=0;for(int i=1;i<65536;++i)cnt[i]=cnt[i^(i&(-i))]+1;
while(m--)
{
read();int k;scanf("%d",&k);
trans(t,tt);bool flag=0;
for(int i=0;i<16&&!flag;++i)
for(int c:ht[i][tt[i]])
{
int res=0;
for(int j=0;j<16;++j)
{
res+=cnt[tt[j]^ss[c][j]];
if(res>k)break;
}
if(res<=k){flag=1;break;}
}
lans=flag;puts(flag?"1":"0");
}
return 0;
}
密码箱
description
略
solution
容易发现只要按照题意进行计算分子分母始终互质,因此在以下的讨论中不用关心这一点。
注意到本题要同时求出分母和分子,这意味着我们需要同时维护这两个东西。可以发现题目中的操作其实就是关于分子分母的线性变换。若用(egin{bmatrix}x\yend{bmatrix}) 表示(dfrac xy) ,考虑在其前面的数为(a_i) ,那么由题知(a_i+dfrac 1{frac xy}=dfrac{a_ix+y}x) ,有(egin{bmatrix}a_i&1\1&0end{bmatrix}egin{bmatrix}x\yend{bmatrix}=egin{bmatrix}a_ix+y\xend{bmatrix}) ,即左乘一个(egin{bmatrix}a_i&1\1&0end{bmatrix}) 即可。因此对于数列(a_0,a_1,cdots,a_k) ,答案就是:
现在的关键就是维护这些矩阵的积了。
对于W
操作,需要给最后一项加一。注意到我们有(egin{bmatrix}a_i&1\1&0end{bmatrix}egin{bmatrix}1&0\k&1end{bmatrix}=egin{bmatrix}a_i+k&1\1&0end{bmatrix}) ,因此直接右乘一个(egin{bmatrix}1&0\1&1end{bmatrix}) 即可。
对于E
操作,分情况进行讨论。若最后两项为(t,1) ,那么第一种变化后为(t+1,1) ,第二种变化为(t,0,1,1) ,这两种变化后的结果分别为:(t+1+dfrac 1{1+frac 1{frac xy}}=dfrac{(t+2)x+(t+1)y}{x+y}) 和(t+dfrac 1{0+frac 1{1+frac 1{1+frac xy}}}=dfrac{(t+2)x+(t+1)y}{x+y}) ,完全一致。因此我们可以只考虑第二种情况,那么相当于右乘的矩阵为:(egin{bmatrix}1&0\-1&1end{bmatrix}egin{bmatrix}1&1\1&0end{bmatrix}egin{bmatrix}1&1\1&0end{bmatrix}=egin{bmatrix}2&1\-1&0end{bmatrix}) 。
两种操作分别对应于右乘一个矩阵。题目中的APPEND
操作显然是易于维护的。至于FLIP
和REVERSE
操作则只需分别维护未FLIP
且未REVERSE
,FLIP
且未REVERSE
,未FLIP
且REVERSE
以及FLIP
且REVERSE
这四种情况下对应的矩阵即可。可以使用Treap轻松维护。
code
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5,mod=998244353;
inline int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
namespace Treap
{
struct mt{int a,b,c,d;inline void pre(){a=b=c=d=0;}};
inline mt operator*(const mt&x,const mt&y)
{
mt z;z.pre();
z.a=add(1ll*x.a*y.a%mod,1ll*x.b*y.c%mod);
z.b=add(1ll*x.a*y.b%mod,1ll*x.b*y.d%mod);
z.c=add(1ll*x.c*y.a%mod,1ll*x.d*y.c%mod);
z.d=add(1ll*x.c*y.b%mod,1ll*x.d*y.d%mod);
return z;
}
const mt M[2]={{1,0,1,1},{2,1,mod-1,0}};
mt19937 rd(time(0));
unsigned int fix[N];int tot,rt,sz[N],ls[N],rs[N];
mt A0[N],A1[N],B0[N],B1[N];bool rev[N],fip[N],vt[N];
inline int nd(bool tp)
{
int p=++tot;sz[p]=1;ls[p]=rs[p]=0;fix[p]=rd();
A0[p]=A1[p]=M[tp],B0[p]=B1[p]=M[tp^1];
rev[p]=fip[p]=0;vt[p]=tp;
return p;
}
inline void up(int u)
{
sz[u]=sz[ls[u]]+1+sz[rs[u]];
bool t=vt[u];
if(ls[u]&&rs[u])
A0[u]=A0[ls[u]]*M[t]*A0[rs[u]],A1[u]=A1[rs[u]]*M[t]*A1[ls[u]],
B0[u]=B0[ls[u]]*M[t^1]*B0[rs[u]],B1[u]=B1[rs[u]]*M[t^1]*B1[ls[u]];
else if(ls[u])
A0[u]=A0[ls[u]]*M[t],A1[u]=M[t]*A1[ls[u]],
B0[u]=B0[ls[u]]*M[t^1],B1[u]=M[t^1]*B1[ls[u]];
else if(rs[u])
A0[u]=M[t]*A0[rs[u]],A1[u]=A1[rs[u]]*M[t],
B0[u]=M[t^1]*B0[rs[u]],B1[u]=B1[rs[u]]*M[t^1];
else A0[u]=A1[u]=M[t],B0[u]=B1[u]=M[t^1];
}
inline void cov1(int u)
{
swap(ls[u],rs[u]);
swap(A0[u],A1[u]),swap(B0[u],B1[u]);
rev[u]^=1;
}
inline void cov2(int u)
{
swap(A0[u],B0[u]),swap(A1[u],B1[u]);
vt[u]^=1;fip[u]^=1;
}
inline void dn(int u)
{
if(rev[u])
{
if(ls[u])cov1(ls[u]);
if(rs[u])cov1(rs[u]);
rev[u]=0;
}
if(fip[u])
{
if(ls[u])cov2(ls[u]);
if(rs[u])cov2(rs[u]);
fip[u]=0;
}
}
void split(int p,int d,int&l,int&r)
{
if(!p)return l=r=0,void();
dn(p);
if(sz[ls[p]]+1<=d)
l=p,split(rs[p],d-1-sz[ls[p]],rs[l],r),up(l);
else r=p,split(ls[p],d,l,ls[r]),up(r);
}
int merge(int x,int y)
{
if(!x||!y)return x|y;
dn(x),dn(y);
if(fix[x]>fix[y])
{
rs[x]=merge(rs[x],y);
up(x);return x;
}
else
{
ls[y]=merge(x,ls[y]);
up(y);return y;
}
}
inline void ins(bool tp){rt=merge(rt,nd(tp));}
inline void grev(int l,int r)
{
int a,b,c;
split(rt,l-1,a,b),split(b,r-l+1,b,c);
cov1(b);rt=merge(merge(a,b),c);
}
inline void gfip(int l,int r)
{
int a,b,c;
split(rt,l-1,a,b),split(b,r-l+1,b,c);
cov2(b);rt=merge(merge(a,b),c);
}
inline void print(){printf("%d %d
",A0[rt].a,A0[rt].c);}
}
using Treap::ins;
using Treap::grev;
using Treap::gfip;
using Treap::print;
char ch[N];
inline bool id(char c){return c=='E';}
int main()
{
int n,q;scanf("%d%d",&n,&q);
scanf("%s",ch+1);ins(0);
for(int i=1;i<=n;++i)ins(id(ch[i]));
print();
while(q--)
{
scanf("%s",ch);
if(ch[0]=='A')
scanf("%s",ch),ins(id(ch[0]));
else
{
int l,r;scanf("%d%d",&l,&r);++l,++r;
if(ch[0]=='F')gfip(l,r);
else grev(l,r);
}
print();
}
return 0;
}