题意:给你一个括号序列。操作1:询问需要更改多少个括号使之匹配。
操作2:反转序列,左括号变成右括号。
操作3:翻转序列,倒置。
标程:
1 #include<cstdio> 2 #include<algorithm> 3 #include<cstring> 4 #define ls son[k][0] 5 #define rs son[k][1] 6 using namespace std; 7 const int N=100005; 8 int sum[N],mn_l[N],mx_l[N],mx_r[N],mn_r[N],a[N],son[N][2],rev[N],tag[N],n,t,l,r,sz[N],y,z,fa[N],Q,rt; 9 char s[N]; 10 void up(int k) 11 { 12 sum[k]=sum[ls]+sum[rs]+a[k];sz[k]=sz[ls]+sz[rs]+1; 13 mx_l[k]=max(mx_l[ls],sum[ls]+a[k]+mx_l[rs]); 14 mn_l[k]=min(mn_l[ls],sum[ls]+a[k]+mn_l[rs]); 15 mx_r[k]=max(mx_r[rs],sum[rs]+a[k]+mx_r[ls]); 16 mn_r[k]=min(mn_r[rs],sum[rs]+a[k]+mn_r[ls]); 17 } 18 void work_rev(int k)//由于需要访问实际pushdown到点的下一层(son[r+1][0]),所以要对下一层的权值进行修改 19 {rev[k]^=1;swap(mx_l[k],mx_r[k]);swap(mn_l[k],mn_r[k]);} 20 void work_opp(int k) 21 { 22 tag[k]^=1; 23 sum[k]=-sum[k];a[k]=-a[k]; 24 mx_l[k]=-mx_l[k];mx_r[k]=-mx_r[k];mn_l[k]=-mn_l[k];mn_r[k]=-mn_r[k]; 25 swap(mx_l[k],mn_l[k]);swap(mx_r[k],mn_r[k]); 26 } 27 void down(int k) 28 { 29 if (rev[k]) 30 { 31 swap(son[k][0],son[k][1]);work_rev(ls);work_rev(rs); 32 rev[k]=0; 33 } 34 if (tag[k]) 35 { 36 work_opp(ls);work_opp(rs); 37 tag[k]=0; 38 } 39 } 40 void rot(int &k,int x) 41 { 42 int y=fa[x],z=fa[y],l=(son[y][1]==x),r=l^1;//函数里面定义l,r,不要和询问的l,r混淆 43 if (y==k) k=x;else son[z][(son[z][1]==y)]=x; 44 fa[y]=x;fa[x]=z;fa[son[x][r]]=y; 45 son[y][l]=son[x][r];son[x][r]=y; 46 up(y);up(x); 47 } 48 void spl(int &k,int x) 49 { 50 for (;x!=k;rot(k,x)) 51 if ((y=fa[x])!=k) 52 if (son[y][0]==x^son[fa[y]][0]==y) rot(k,x);else rot(k,y); 53 } 54 void build(int l,int r,int f) 55 { 56 if (l>r) return; 57 int mid=(l+r)>>1;fa[mid]=f; 58 if (f) son[f][(mid>f)]=mid; 59 if (s[mid]=='(') a[mid]=1;else if (s[mid]==')') a[mid]=-1; 60 if (l==r) 61 { 62 sz[l]=1;sum[l]=a[l]; 63 if (a[l]>0) mx_l[l]=mx_r[l]=1; 64 if (a[l]<0) mn_l[l]=mn_r[l]=-1; 65 return; 66 } 67 build(l,mid-1,mid);build(mid+1,r,mid); 68 up(mid); 69 } 70 int find(int k,int x)//因为有翻转操作,所以区间第x个与原下标为x的不对应 71 { 72 down(k); 73 if (x==sz[ls]+1) return k; 74 if (x<=sz[ls]) return find(ls,x);else return find(rs,x-sz[ls]-1); 75 } 76 int main() 77 { 78 scanf("%d%d%s",&n,&Q,s+2);s[1]='*';s[n+2]='*'; 79 build(1,n+2,0);rt=(n+3)/2; 80 while (Q--) 81 { 82 scanf("%d%d%d",&t,&l,&r); 83 l=find(rt,l);r=find(rt,r+2); 84 spl(rt,l);spl(son[rt][1],r); int now=son[r][0]; 85 if (t==0) printf("%d\n",(abs(mn_l[now])+1)/2+(abs(mx_r[now])+1)/2); 86 else if (t==1) work_opp(now); 87 else work_rev(now); 88 } 89 return 0; 90 }
易错点:1.由于需要访问实际pushdown到点的下一层(son[r+1][0]),所以要对下一层的权值进行修改 。pushdown的写法应根据实际情况来。
2.函数里面定义l,r,不要和询问的l,r混淆。
3.因为有翻转操作,所以区间第x个与原下标为x的不对应。所以要find到区间第x个数(二叉排序树维护区间位置)。
题解:splay
你看有翻转为什么不用splay呢?
对于一串括号序列,将中间的匹配掉后剩下x个右括号和y个左括号。(x+y)是偶数时才可能完全匹配。此时如果x,y全偶,那么修改x/2+y/2个即可,全奇,修改(x+1)/2+(y+1)/2个即可。问题在于怎么求x和y。
有性质,x=由左端点固定,右端点在[l,r]中的括号子序列中(右括号-左括号)的最大值,y=由右端点固定,左端点在[l,r]中的括号子序列中(左括号-右括号)的最大值。
用+1-1表示左右括号,x=左边最大,y=右边最小。
反转,就需要维护再维护左边最小和右边最大。用打标记的方法,标记下传时对四个关键值取负,并把左边/右边的最小最大交换。
翻转维护rev标记即可。