题解:P5226([SCOI2015] 小凸解密码)

1. Description

小凸得到了一个密码盘,密码盘被等分成 \(n\) 个扇形,每个扇形上有一个数字 \((0 \sim 9)\),和一个符号 \((\) + 或 * \()\)。密码盘解密的方法如下:

首先,选择一个位置开始,顺时针地将数字和符号分别记在数组 \(A\) 和数组 \(C\) 中。解密的方法如下:

  • \(B_0 = A_0\)
  • \(x > 0\) 时:
    • \(C_x\) 为 +,\(B_x = (A_x + A_{x - 1}) \bmod 10\)
    • \(C_x\) 为 *,\(B_x = (A_x \times A_{x - 1}) \bmod 10\)

操作完成后,可以得到一个长度为 \(n\) 的数组 \(B\),然后以 \(B_0\) 为起点将 \(B\) 数组顺时针写成一个环,解密就完成了,称得到的环为答案环。

现在小凸得到了一份指令表,指令表上有 2 种操作。一种指令是修改操作,即改变原来密码盘上一个位置的数字和符号。另一种指令是询问操作,具体如下:

  • 首先从指令给出的位置开始完成解密,得到答案环。
  • 答案环上会有一些 \(0\) 连在一起,将这些连在一起的 \(0\) 称为零区间,找出其中距离 \(B_0\) 最远的那个零区间,输出这个距离(零区间和 \(B_0\) 的距离定义为:零区间内所有 \(0\)\(B_0\) 距离中的最小值)。

2. Solution

首先不妨来思考这道题的简化版,就是每一次询问的起始位置都是 \(0\)

想到对于一个零区间 \((l,r)\),它到 \(0\) 的距离为 \(\min(l,n-r)\),所以可以尝试维护起始位置为 \(0\) 的所有零区间 \((l,r)\)

而对于一个修改,最多只会修改 \(x\)\(x+1\) 两个位置的值,所以维护起来是简单的,这里不过多赘述了,具体实现可以看代码。

对于询问,我们不难想到去除 \(\min\),因此将所有区间分为三类:

  1. \(l,r\in[0,\frac{n}{2}]\),距离为 \(l\)
  2. \(l,r\in [\frac{n}{2}+1,n-1]\),距离为 \(n-r\)
  3. \(l\in [0,\frac{n}{2}],r\in [\frac{n}{2}+1,n-1]\),距离为 \(\min(l,n-r)\)

因为所有零区间不会有交,所以第三类的区间最多只有一个,特判即可。

对于第一类区间,我们需要求出 \(l\) 最大的区间,对于第二类区间,我们需要求出 \(r\) 最小的区间,所以可以使用一个 set 维护所有零区间,然后二分查找出对应区间即可。

然后考虑起始位置不一致的情况,令起始位置为 \(pos\)

此时 \(pos\)\(0\) 的值会发生变化,并且如果存在两个区间 \((x,n-1)\)\((0,y)\),它们实际上应该并成同一个区间 \((x,y)\),如果存在一个区间 \((x,y)\) 包含 \(pos\),它实际上应该被分成两个区间 \((x,pos-1)\)\((pos,y)\)

然后所以区间应该被分成如下三类:

  1. \(l,r\in [pos,pos+\frac{n}{2}]\)
  2. \(l,r\in [pos+\frac{n}{2}+1,pos+n-1]\)
  3. \(l\in [pos,pos+\frac{n}{2}],r\in[pos+\frac{n}{2}+1,pos+n-1]\)

最后注意一下分类讨论,求解即可,代码中有详细的注释,可供参考。

3. Code

#include<bits/stdc++.h>
#define pii pair<int,int>
#define Name 838412064
#define raed(x) read(x)
#define Nxt puts("")
#define Spa putchar(32)
#define Pline puts("------------------------------")
namespace FastIO{
	int write_top,read_f,read_x;
	char read_char;
	int write_st[20];
	inline int read(int &a){
		read_char=getchar();
		read_f=1;
		a=0;
		while(!isdigit(read_char)){
			if(read_char=='-')read_f=-1;
			read_char=getchar();
		}
		while(isdigit(read_char)){
			a=(a<<1)+(a<<3)+(read_char^48);
			read_char=getchar();
		}
		return a=a*read_f;
	}
	inline int read(){
		read_char=getchar();
		read_f=1;
		read_x=0;
		while(!isdigit(read_char)){
			if(read_char=='-')read_f=-1;
			read_char=getchar();
		}
		while(isdigit(read_char)){
			read_x=(read_x<<1)+(read_x<<3)+(read_char^48);
			read_char=getchar();
		}
		return read_x*read_f;
	}
	inline void write(int x){
		if(x<0)putchar('-'),x=-x;
		write_top=0;
		do{
		   write_st[++write_top]=x%10;
		   x/=10;
		}while(x);
		while(write_top)putchar(write_st[write_top--]+'0');
		return ;
	}
	inline void tomax(int &a,int b){
		if(a<b)a=b;
		return ;
	}
	inline void tomin(int &a,int b){
		if(a>b)a=b;
		return ;
	}
}
using namespace FastIO;
using namespace std;
const int N=1e5+5;
int n,m,half;
int A[N],B[N];
char C[N];
set<pii>st;
struct Node{
	pii a,b,c;
	bool opt;
}Mod[5];
void change(int x,int val){//将 B_x 设为 val 
	if(B[x]==0){//B_x 本来是 0,将区间分成 (l,x-1),(x+1,r) 
		auto it=st.upper_bound({x,n});
		auto tmp=*(--it);
		st.erase(it); 
		if(tmp.first<x)st.insert({tmp.first,x-1});
		if(x<tmp.second)st.insert({x+1,tmp.second});
	}
	B[x]=val;
	if(B[x]==0){//B_x 修改之后是 0,尝试合并两边的区间 
		auto it=st.insert({x,x}).first;
		int nowl=x,nowr=x;
		if(next(it)!=st.end()){
			auto tmp=*next(it);
			if(tmp.first==x+1){
				st.erase(it);
				st.erase(tmp);
				it=st.insert({nowl,tmp.second}).first;
				nowr=tmp.second;
			}
		}
		if(it!=st.begin()){
			auto tmp=*prev(it);
			if(tmp.second==x-1){
				st.erase(it);
				st.erase(tmp);
				it=st.insert({tmp.first,nowr}).first;
				nowl=tmp.first;
			}
		}
	}
}
void modify(int x){//要修改 x 和 x+1 的值  
	int num;
	char opt;
	read(num),opt=getchar();
	while(opt!='+'&&opt!='*')opt=getchar();
	A[x]=num,C[x]=opt;
	int val;
	if(x==0)val=A[0];
	else val=(C[x]=='+'?A[x]+A[x-1]:A[x]*A[x-1])%10;
	change(x,val);
	if(x!=n-1){
		val=(C[x+1]=='+'?A[x+1]+A[x]:A[x+1]*A[x])%10;
		change(x+1,val);
	}
}
int dist(int x,int y){//求之间两个位置的距离 
	if(x==y)return 0;
	if(x<y)return min(y-x,n+x-y);
	return min(x-y,n+y-x);
}
int query(int x){
	if(x==0){//起始位置为 0 的特判 
		if(st.size()==0)//没有零区间,答案为 0 
			return -1;
		if(st.size()==1)//只有一个零区间,可以直接算  
			return min(dist(0,st.begin()->first),dist(0,st.begin()->second));
		int res=-1;
		/*
		将区间分为三类 
		1.l,r\in [0,half]
		2.l,r\in [half+1,n-1]
		2.l\in [0,half],r\in [half+1,n-1]
		*/
		//求第一类 + 第三类   
		auto it=st.upper_bound({half,n});
		//所有满足 l<=half 的 (l,r) 都比 (half,n) 小
		//所以 it 的前一个就是第一类的最后一个区间(或者是第三类的区间)  
		if(it!=st.begin()){
			it--;
			if(it->second>half){//由于 r > half,所以这是第三类区间  
				tomax(res,min(dist(0,it->first),dist(0,it->second)));
				if(it!=st.begin()){
					it--;//由于第三类区间只有一个,所以这个肯定是第一类的区间了  
					tomax(res,dist(0,it->first));
				}
			}else tomax(res,dist(0,it->first));//否则这个就是第一类的区间  
		} 
		//求第二类  
		it=st.upper_bound({half,n});
		//所有满足 l>half 的 (l,r) 都比 (half,n) 大
		//所以 it 就是第二类的第一个  
		if(it!=st.end())tomax(res,dist(x,it->second));
		return res;
	}
	//修改 0 和 x 位置的值  
	change(0,(C[0]=='+'?A[0]+A[n-1]:A[0]*A[n-1])%10);
	change(x,A[x]);
	
	int cnt=0;
	if(B[x]==0){//将包含 x 的区间分开  
		auto it=st.upper_bound({x,n});
		//求出 l>x 的第一个区间,前一个就是包含 x 的区间  
		it--;
		if(it->first!=x){
			cnt++;//记录修改方便最后撤销  
			Mod[cnt].opt=1;
			Mod[cnt].a=*it;
			Mod[cnt].b={it->first,x-1};
			Mod[cnt].c={x,it->second};
			st.erase(it);
			st.insert(Mod[cnt].b);
			st.insert(Mod[cnt].c);	
		}
	}
	if(B[0]==0){//将 (x,n-1) (0,y) 合并为 (x,y) 
		if(st.rbegin()->second==n-1){//要满足右端点为 n-1 
			cnt++;
			Mod[cnt].opt=0;
			Mod[cnt].a=*st.rbegin();
			Mod[cnt].b=*st.begin();
			Mod[cnt].c={Mod[cnt].a.first,Mod[cnt].b.second};
			st.erase(*st.rbegin());
			st.erase(*st.begin());
			st.insert(Mod[cnt].c);
		}
	}	
	//下面两个特判和 x=0 时是一样的  
	if(st.size()==1){
		int res=min(dist(x,st.begin()->first),dist(x,st.begin()->second));
		//注意撤销的先后顺序,要从后往前撤销 
		for(int j=cnt;j>=1;j--){ 
			if(Mod[j].opt==1){
				st.erase(Mod[j].b);
				st.erase(Mod[j].c);
				st.insert(Mod[j].a);
			}else{
				st.erase(Mod[j].c);
				st.insert(Mod[j].a);
				st.insert(Mod[j].b);
			}
		}
		change(0,A[0]);
		change(x,(C[x]=='+'?A[x]+A[x-1]:A[x]*A[x-1])%10);
		return res;
	}
	if(st.size()==0){
		for(int j=cnt;j>=1;j--){
			if(Mod[j].opt==1){
				st.erase(Mod[j].b);
				st.erase(Mod[j].c);
				st.insert(Mod[j].a);
			}else{
				st.erase(Mod[j].c);
				st.insert(Mod[j].a);
				st.insert(Mod[j].b);
			}
		}
		change(0,A[0]);
		change(x,(C[x]=='+'?A[x]+A[x-1]:A[x]*A[x-1])%10);
		return -1;
	}
	int res=-1;
	if(x+half<n){
		/*
		区间分为三类 
		1. l,r\in [x,x+half]
		2. l,r\in [x+half,n-1] and [0,x-1]
		3. l\in [x,x+half] r\in [x+half,n-1] and [0,x-1]
		*/
		//求第一类 + 第三类 
		auto it=st.upper_bound({x+half,n});
		if(it!=st.begin()){
			it--;
			if(it->first>=x){
				if(it->second>x+half||it->second<x){//是第三类区间  
					tomax(res,min(dist(x,it->first),dist(x,it->second)));
					if(it!=st.begin()){
						it--;//同理,向前一个必定是第一类区间  
						if(it->first>=x)tomax(res,dist(x,it->first));
					}
				}else tomax(res,dist(x,it->first));	
			}
		}
		//求第二类  
		it=st.upper_bound({x+half,n});
		if(it!=st.end())tomax(res,dist(x,it->second));
		else if(st.begin()->first<x)tomax(res,dist(x,st.begin()->second));
		//如果这个区间是 (l,r) r>=x 的话,这个区间应该被分成两个
		//所以这个区间必然是第二类的区间 
	}else{
		/*
		区间分为三类 
		1. l,r\in [x,n-1] and [0,x+half-n]
		2. l,r\in [x+half-n+1,x-1]
		3. l\in [x,n-1] and [0,x+half-n] r\in [x+half-n+1,x-1]
		*/
		auto it=st.upper_bound({x+half-n,n});
		if(it!=st.begin()){
			it--;
			if(x+half-n<it->second&&it->second<x){//是第三类的区间  
				tomax(res,min(dist(x,it->first),dist(x,it->second)));
				if(it!=st.begin()){
					//如果不是第一个的话,第一类的最后一个就是它的前一个  
					it--;
					tomax(res,dist(x,it->first));
				}else{//否则,就是最后一个  
					it=st.end();
					it--;
					if(it->first>=x)//注意判断是否合法  
						tomax(res,dist(x,it->first));
				}
			}else tomax(res,dist(x,it->first));//否则就是这个  
		}else{//这里同理,应该是最后一个 
			it=st.end();
			it--;
			if(it->first>=x){//同样判断是否合法 
				//这里跟前面类似  
				if(x+half-n<it->second&&it->second<x){
					tomax(res,min(dist(x,it->first),dist(x,it->second)));
					if(it!=st.begin()){
						it--;
						if(it->first>=x)
							tomax(res,dist(x,it->first));
					}
				}else tomax(res,dist(x,it->first));
			}
		}
		//求第二类  
		it=st.upper_bound({x+half-n,n});
		if(it!=st.end())
			if(it->second<x)//注意是否合法  
				tomax(res,dist(x,it->second));
	}	
	//同样要从后往前撤销  
	for(int j=cnt;j>=1;j--){
		if(Mod[j].opt==1){
			st.erase(Mod[j].b);
			st.erase(Mod[j].c);
			st.insert(Mod[j].a);
		}else{
			st.erase(Mod[j].c);
			st.insert(Mod[j].a);
			st.insert(Mod[j].b);
		}
	}
	change(0,A[0]);
	change(x,(C[x]=='+'?A[x]+A[x-1]:A[x]*A[x-1])%10);
	return res;
}
signed main(){
	read(n),read(m);
	for(int i=0;i<n;i++){
		read(A[i]);
		C[i]=getchar();
		while(C[i]!='+'&&C[i]!='*')C[i]=getchar();
	}
	half=n/2;
	B[0]=A[0];
	for(int j=1;j<n;j++)
		B[j]=(C[j]=='+'?A[j]+A[j-1]:A[j]*A[j-1])%10;
	for(int j=0,now;j<n;j++){
		if(B[j]==0){
			now=j;
			while(now+1<n&&B[now+1]==0)
				now++;
			st.insert({j,now});
			j=now;
		}
	}
	for(int i=1,opt,x,num,ans,cnt;i<=m;i++){
		read(opt);
		if(opt==1){
			read(x);
			modify(x);
		}else{
			read(x);
			write(query(x)),Nxt;
		}
	}
}
posted @ 2025-11-28 09:39  陈牧九  阅读(0)  评论(0)    收藏  举报