NOIP2023模拟16联测37 小猫吃火龙果

文章讲述了如何解决一个关于字母关系的问题,通过分块和预处理,针对区间修改和查询操作优化时间复杂度,达到在给定限制下的最优解。

题目大意

给你一个长度为nnn的序列,每个位置是A,B,CA,B,CA,B,C三个字母之一。

AAA可以打败BBBBBB可以打败CCCCCC可以打败AAA

mmm次操作,操作有两种类型:

  • l r x y:将区间[l,r][l,r][l,r]内的所有xxx改成yyyyyy改成xxx
  • l r x:你手中拿着字母xxx,要从lll走到rrr,每次你需要对比你手上的字母和当前序列位置的字母,如果当前序列位置的字母可以吃掉你手上的字母,则将你手上的字母改为当前序列位置的字母。这类操作对序列没有任何修改。求走完了[l,r][l,r][l,r]之后,你手中的物品是什么

1≤n,m≤2×1051\leq n,m\leq 2\times 10^51n,m2×105

时间复杂度为5000ms5000ms5000ms,空间复杂度为512MB512MB512MB


题解

考虑分块。设块长为BBB

维护每一块中,在每种对应关系下进入的字母为A/B/CA/B/CA/B/C时走完这个块出来之后是什么字母。设当前块为vvv,对应关系为v1v1v1,进入的字母为v2v2v2,则记出来的字母为wv,v1,v2w_{v,v1,v2}wv,v1,v2,因为对应关系有3!=63!=63!=6种,字母有333种,所以我们预处理出所有wv,v1,v2w_{v,v1,v2}wv,v1,v2的时间复杂度为O(18n)O(18n)O(18n)

对于第一类操作,即区间修改,对散块暴力修改并更新www值,时间复杂度为O(36B)O(36B)O(36B);对整块只需要改变块中的对应关系即可,时间复杂度为O(3nB)O(\dfrac{3n}{B})O(B3n)

对于第二类操作,即区间查询,对散块暴力查询,时间复杂度为O(2B)O(2B)O(2B);对整块直接用wv,v1,v2w_{v,v1,v2}wv,v1,v2查询即可,时间复杂度为O(nB)O(\dfrac nB)O(Bn)

所以,总时间复杂度为O(q(36B+3nB))O(q(36B+\dfrac{3n}{B}))O(q(36B+B3n)),由基本不等式可得当36B=3nB36B=\dfrac{3n}{B}36B=B3n时时间复杂度最小,即B=n12B=\sqrt{\dfrac{n}{12}}B=12n,那么时间复杂度为O(312qn)O(3\sqrt{12}q\sqrt n)O(312qn)。因为跑不满,而且时限有5s5s5s,所以是可以过的。

可以参考代码帮助理解。

code

#include<bits/stdc++.h>
using namespace std;
const int N=200000,V=2000;
int n,m,bl,a[N+5],be[V+5][3],re[3][3][3],pd[3][3];
int to[6][3]={{0,1,2},{0,2,1},{1,0,2},{1,2,0},{2,0,1},{2,1,0}};
int w[V+5][6][3];
char s[N+5];
char rdc(){
	char ch=getchar();
	while(ch<'A'||ch>'C') ch=getchar();
	return ch;
}
int pos(int i){
	return (i-1)/bl+1;
}
int gtpd(int x,int y){
	if(x==0&&y==1||x==1&&y==2||x==2&&y==0) return 1;
	if(x==y) return 0;
	return -1;
}
int wh(int v){
	return re[be[v][0]][be[v][1]][be[v][2]];
}
void gtw(int v){
	for(int v1=0;v1<6;v1++){
		for(int v2=0;v2<3;v2++){
			int vt=v2;
			for(int i=v*bl-bl+1;i<=min(v*bl,n);i++){
				if(pd[to[v1][a[i]]][vt]==1) vt=to[v1][a[i]];
			}
			w[v][v1][v2]=vt;
		}
	}
}
void init(){
	for(int i=0;i<3;i++){
		for(int j=0;j<3;j++) pd[i][j]=gtpd(i,j);
	}
	for(int i=0;i<6;i++){
		re[to[i][0]][to[i][1]][to[i][2]]=i;
	}
	for(int i=1;i<=pos(n);i++){
		gtw(i);
		be[i][0]=0;be[i][1]=1;be[i][2]=2;
	}
}
void work(int l,int r,int x,int y){
	int vl=pos(l);
	for(int i=vl*bl-bl+1;i<=min(vl*bl,n);i++){
		a[i]=be[vl][a[i]];
	}
	be[vl][0]=0;be[vl][1]=1;be[vl][2]=2;
	for(int i=l;i<=r;i++){
		if(a[i]==x) a[i]=y;
		else if(a[i]==y) a[i]=x;
	}
	gtw(vl);
}
void ch(int l,int r,int x,int y){
	int vl=pos(l),vr=pos(r);
	if(vl==vr){
		work(l,r,x,y);return;
	}
	work(l,vl*bl,x,y);
	work(vr*bl-bl+1,r,x,y);
	for(int i=vl+1;i<=vr-1;i++){
		for(int j=0;j<3;j++){
			if(be[i][j]==x) be[i][j]=y;
			else if(be[i][j]==y) be[i][j]=x;
		}
	}
}
int find(int l,int r,int x){
	int vl=pos(l),vr=pos(r);
	if(vl==vr){
		for(int i=l;i<=r;i++){
			if(pd[be[vl][a[i]]][x]==1) x=be[vl][a[i]];
		}
		return x;
	}
	for(int i=l;i<=vl*bl;i++){
		if(pd[be[vl][a[i]]][x]==1) x=be[vl][a[i]];
	}
	for(int i=vl+1;i<=vr-1;i++){
		x=w[i][wh(i)][x];
	}
	for(int i=vr*bl-bl+1;i<=r;i++){
		if(pd[be[vr][a[i]]][x]==1) x=be[vr][a[i]];
	}
	return x;
}
int main()
{
//	freopen("training.in","r",stdin);
//	freopen("training.out","w",stdout);
	scanf("%d%d",&n,&m);
	if(n<=100) bl=sqrt(n);
	else bl=sqrt(n/12);
	scanf("%s",s+1);
	for(int i=1;i<=n;i++) a[i]=s[i]-'A';
	init();
	while(m--){
		int tp,l,r;
		char x,y;
		scanf("%d",&tp);
		if(tp==0){
			scanf("%d%d",&l,&r);
			x=rdc();y=rdc();
			ch(l,r,x-'A',y-'A');
		}
		else{
			scanf("%d%d",&l,&r);
			x=rdc();
			printf("%c\n",find(l,r,x-'A')+'A');
		}
	}
	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值