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\),因此将所有区间分为三类:
- \(l,r\in[0,\frac{n}{2}]\),距离为 \(l\)。
- \(l,r\in [\frac{n}{2}+1,n-1]\),距离为 \(n-r\)。
- \(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)\)。
然后所以区间应该被分成如下三类:
- \(l,r\in [pos,pos+\frac{n}{2}]\)。
- \(l,r\in [pos+\frac{n}{2}+1,pos+n-1]\)。
- \(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;}}
}