题解: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\),因此将所有区间分为三类:
- \(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;
}
}
}

浙公网安备 33010602011771号