NOI2021部分题目题解

Day 1

轻重边

description

给出一棵树,初始时每条边都是轻边。有\(m\) 次操作。操作分如下两种:

  • 给定两点\(a,b\) ,先对于\(a,b\) 路径上所有点\(x\) ,将与\(x\) 相连的边全部变为轻边,然后再将处于\(a,b\) 路径上的边变为重边。(修改)
  • 给定两点\(a,b\) ,询问其路径上有多少条重边。(询问)

\(n,m\le 10^5\)

solution

我们可以从点的方面进行考虑。考虑每次修改时将对应路径上所有点都标记上一个新的时间戳,那么一条边是重边当且仅当其两端点的时间戳相同。(因此初始时需要特殊构造)那么询问可以转化为有多少对相邻点颜色相同,这个可以使用线段树进行维护。于是树链剖分+线段树即可,稍微注意下边界情况。

复杂度\(\mathcal O(n\log^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\n",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 i\in[2,k-1],n_i\in[n_1,2n_1]\) ,表示第\(i\) 层恰好有\(n_i\) 个点,编号为\(1\sim n_i\)。而后给出边,所有边都满足起点在第\(i\) 层而终点在第\(i+1\) 层,其中\(i\in[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\) 取模后的值。

\(n1\le 100,k\le 100\)

solution

不相交路径让我们联想到\(LGV\) 引理,它是这样描述的,令

\[M=\left[\begin{matrix}e(A_1,B_1)&e(A_1,B_2)&\cdots&e(A_1,B_n)\\e(A_2,B_1)&e(A_2,B_2)&\cdots&e(A_2,B_n)\\\vdots&\vdots&\ddots&\vdots\\e(A_n,B_1)&e(A_n,B_2)&\cdots&e(A_n,B_n)\end{matrix}\right] \]

其中\(A_1,A_2,\cdots,A_n\)\(B_1,B_2,\cdots,B_n\) 分别表示起点和终点,\(e(A_i,B_j)\) 表示从第\(i\) 个起点到第\(j\) 个终点的方案数,那么有

\[\det(M)=\sum_p(-1)^{N(p)} \]

其中\(p\) 表示\(1\sim 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\n",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,q\le 3\times 10^5,k\le 2\)

solution

容易想到先将图\(G\) 进行缩点,因为对于一个强连通分量里的点来说它们的可被经过性是一致的。

\(G\) 有奇怪的性质,我们考虑这个性质应该如何使用。若缩点后\(x\) 能通过一条边就到达\(y\) ,记\(x\rightarrow y\) 。缩点后原图变为一个\(DAG\) ,对于点\(z\) ,若\(\exist x,y\ \text{s.t.}x\rightarrow z,y\rightarrow z\) ,那么必然有\(x\rightarrow y\)\(y\rightarrow x\) 。不妨设为\(x\rightarrow y\) ,那么可以发现若删去边\(x\rightarrow z\) 是不会对答案产生影响的。通过这种方法,我们可以将这个\(DAG\) 转化为一棵外向树。

由于\(k\le 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),'\n');
		}
	}
}
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\) 位不同。强制在线了个寂寞。

\(n\le 4\times 10^5,q\le 1.2\times 10^5,k\le 16\)

solution

注意到\(k\le 15\) ,于是我们便想在\(k\) 上面做文章以优化暴力比较。

我们可以将长度为\(256\)\(01\) 串分割为\(16\) 个子串,每个子串长度为\(16\) 。根据抽屉原理,若某个询问串满足条件,那么必然有一个子串是相同的。

发现数据随机,于是我们可以使用vector记下第\(i\) 个子串为\(j\) 的原串的编号。这样询问时就可以枚举是第几个子串相同进而一一枚举进行比较。通过预处理可以\(\mathcal O(1)\) 得到和某个子串相差多少位。这样下来平均运算次数为\(m\cdot 16\times \dfrac n{2^{16}}\cdot 16\approx 2\times 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

容易发现只要按照题意进行计算分子分母始终互质,因此在以下的讨论中不用关心这一点。

注意到本题要同时求出分母和分子,这意味着我们需要同时维护这两个东西。可以发现题目中的操作其实就是关于分子分母的线性变换。若用\(\begin{bmatrix}x\\y\end{bmatrix}\) 表示\(\dfrac xy\) ,考虑在其前面的数为\(a_i\) ,那么由题知\(a_i+\dfrac 1{\frac xy}=\dfrac{a_ix+y}x\) ,有\(\begin{bmatrix}a_i&1\\1&0\end{bmatrix}\begin{bmatrix}x\\y\end{bmatrix}=\begin{bmatrix}a_ix+y\\x\end{bmatrix}\) ,即左乘一个\(\begin{bmatrix}a_i&1\\1&0\end{bmatrix}\) 即可。因此对于数列\(a_0,a_1,\cdots,a_k\) ,答案就是:

\[ans=(\prod_{i=0}^k\begin{bmatrix}a_i&1\\1&0\end{bmatrix})\cdot\begin{bmatrix}1\\0\end{bmatrix} \]

现在的关键就是维护这些矩阵的积了。

对于W操作,需要给最后一项加一。注意到我们有\(\begin{bmatrix}a_i&1\\1&0\end{bmatrix}\begin{bmatrix}1&0\\k&1\end{bmatrix}=\begin{bmatrix}a_i+k&1\\1&0\end{bmatrix}\) ,因此直接右乘一个\(\begin{bmatrix}1&0\\1&1\end{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}\) ,完全一致。因此我们可以只考虑第二种情况,那么相当于右乘的矩阵为:\(\begin{bmatrix}1&0\\-1&1\end{bmatrix}\begin{bmatrix}1&1\\1&0\end{bmatrix}\begin{bmatrix}1&1\\1&0\end{bmatrix}=\begin{bmatrix}2&1\\-1&0\end{bmatrix}\)

两种操作分别对应于右乘一个矩阵。题目中的APPEND操作显然是易于维护的。至于FLIPREVERSE操作则只需分别维护未FLIP且未REVERSEFLIP且未REVERSE,未FLIPREVERSE以及FLIPREVERSE这四种情况下对应的矩阵即可。可以使用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\n",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;
}
posted @ 2021-08-23 17:33  BILL666  阅读(77)  评论(0编辑  收藏  举报